Reprint please specify the source http://blog.csdn.net/zhaokaiqiang1992
Welcome to Ndroid-tech-frontier Open Source project, regular translation of foreign Android high-quality technology, open Source Library, software architecture design, testing and other articles
- Translator: Zhaokaiqiang
- Reviewer: chaossss
- Status: Proofreading Complete
There are a lot of posts online about the Rxjava Getting Started guide, some of which are based on the Android environment. But I think so far, a lot of people are just addicted to what they see, and when it comes to solving specific problems in their Android projects, they don't know how or why to use Rxjava. In this series of articles, I want to explore patterns in some of the Android projects I've worked with that rely on the Rxjava architecture.
At the beginning of the article, I wanted to deal with some of the situations that Android developers were prone to encounter when using Rxjava. From this perspective, I will provide a more advanced and more appropriate solution. In this series of articles, I would like to hear other developers solve similar problems in the process of using Rxjava, perhaps they are the same as I found.
Issue One: Background tasks
The first challenge for Android developers is how to work effectively in a background thread and then update the UI in the UI thread. This is often due to the need to get data from a Web service. For those who already have relevant experience, you might say, "What's the challenge?" You just need to start a asynctask, and then all the work it's done for you. "If that's what you think, then you have a chance to improve the situation. This is because you are accustomed to this complex way and forget that this should be very concise, or that you are not dealing with all the boundaries that should be handled. Let's talk about this.
Default solution: Asynctask
Asynctask is the default processing tool in Android, and developers can do some long-time work on it without blocking the user interface. (Note: Recently, Asynctaskloader is used to handle some more specific data loading tasks, and we'll talk about this later)
On the surface, it seems very simple, you define some code to run in a background thread, and then define some code to run in the UI thread, and after the background task is processed, it will handle the execution results passed from the background task in the UI thread.
private class CallWebServiceTask extends AsyncTask<String, Result, Void> { protected Result doInBackground(String... someData) { Result result = webService.doSomething(someData); return result; } protected void onPostExecute(Result result) { if (result.isSuccess() { resultText.setText("It worked!"); } }}
The biggest problem with using Asynctask is the handling of the details, let's talk about it.
Error handling
The first question of this simple usage is: "What if there is an error?" "Unfortunately, there is no very good solution for the time being. So a lot of developers eventually inherit Asynctask, then wrap a try/catch in Doinbackground () and return a
The life cycle of activity and fragment
The other question you have to face is: "What happens when Asynctask is running, if I quit the activity or rotate the device?" "Well, if you just send some data and then no longer care about sending the results, that might not be a problem, but what if you need to update the UI based on the result of the task's return?" If you do not do something to prevent it, then when you try to invoke activity or view, you will get a null pointer exception that causes the program to crash because they are now invisible or null.
Similarly, Asynctask did not do much work to help you on this issue. As a developer, you need to be sure to keep a reference to a task, and either cancel the task when the activity is being destroyed, or when you try to update the UI in OnPostExecute (), make sure the activity is in a state of reach. When you just want to do something clearly and make it easier to maintain, it's going to be more difficult to maintain the project.
Cache at rotation (or other cases)
What happens when your users are still in the current activity, just rotating the screen? In this case, canceling the task does not make any sense, because you will eventually need to restart the task after the rotation. Or you don't want to restart the task because the situation has mutated in some places in a non-episodic manner (because it mutates some state somewhere on a non-idempotent way), but you really want to get the results, Because then you can update the UI to reflect the situation.
If you specialize in a read-only loading operation, you can use Asynctaskloader to solve this problem. But it's still heavy for standard Android, because it lacks error handling, doesn't cache in activity, and has many unique quirks.
Multiple Web server calls that are combined
Now, if we have managed to solve the problem above, we need to do some continuous network requests, each of which needs to be based on the results of the previous request. Or, we want to do some concurrent network requests and then merge the results together and send them to the UI thread, but again, sorry, Asynctask can't help you here.
Once you start doing something like this, as more complex threading models grow, the problems that precede them will lead to handling such things very painfully and weakly. If you want these threads to run together, either you let them run separately, and then callback, or in other cases, let them run synchronously in a background thread, and finally the replication composition is different. (to chain calls together, you either keep them separate and end up in callback hell, or run them synchronously together in One background task end up duplicating work to compose them differently in other situations.)
If you want to run in parallel, you must create a custom executor and then pass it to Asynctasktask, because the default asynctask does not support parallelism. and in order to coordinate parallel threads, you need to use Countdownlatchs, Threads, executors and futures to reduce the more complex synchronization patterns.
Testability
Aside from that, if you like to test your code, Asynctask doesn't help you. If we do not do extra work, testing asynctask is very difficult, it is fragile and difficult to maintain. This is a post on how to successfully test Asynctask.
Better solution: RxJava ' s Observable
Fortunately, as soon as we decided to use the Rxjava dependency library, the problems we discussed were solved. Let's see what it can do for us.
Below we will use observables to write a request code instead of the Asynctask method above. (If you use retrofit, then you should be very easy to use, second it also supports observable return value, and it works in a backend thread pool without your extra work)
webService.doSomething(someData).observeOn(AndroidSchedulers.mainThread()).subscribe( result -> resultText.setText("It worked!")), e -> handleError(e));
Error handling
You may notice that without doing extra work, we have dealt with the success and error situations that Asynctask will not handle, and we have written very little code. The additional components you see are the results we want observer to process in the main thread of the UI. This will allow us to move forward a little. And if your Sebservice object doesn't want to run in a background thread, you can also declare it here by using the. Subscribeon (...). (Note: These examples are using the lambda syntax of Java 8, which can be used in Android projects using RETROLAMBDA, but in my opinion the payoff is higher than the risk, and we prefer to use it in our projects compared to writing this article.) )
The life cycle of activity and fragment
Now, we want to use rxandroid to solve the life cycle problem mentioned above, we don't need to specify Mainthread () scheduler (by the way, you only need to import rxandroid). Just like this.
AppObservable.bindFragment(this, webService.doSomething(someData)).subscribe( result -> resultText.setText("It worked!")), e -> handleError(e));
My usual practice is to create a helper method in the application's base fragment to simplify this, and you can refer to a base rxfragment I maintain to get some guidance.
bind(webService.doSomething(someData)).subscribe( result -> resultText.setText("It worked!")), e -> handleError(e));
If our activity or fragment is no longer in the reach state, then appobservable.bindfragment () can insert a shim in the call chain to prevent OnNext () from running. If the Activity, fragment is unreachable when the task is trying to run, subscription will unsubscribe and stop running, so there is no risk of a null pointer and the program will not crash. One thing to note is that when we leave activity and fragment, we are temporarily or permanently leaked, depending on the observable behavior of the problem. So in the bind () method, I also invoke the lifecycleobservable mechanism, which is automatically canceled when fragment is destroyed. The advantage of doing this is once and for all.
So, this solves the first two problems, but the following one is the place where Rxjava Dafa.
Multiple Web server calls that are combined
I won't explain it in detail here, because it's a complex problem, but by using observables, you can do complex things in a very simple and easy-to-understand way. Here is an example of a chained Web service invocation that relies on each other, runs a second batch of parallel calls in the thread pool, and then merges and sorts the data before returning the results to observer. For a better measurement, I even put a filter inside. All business logic is in the following short five lines of code inside ...
public Observable<List<CityWeather>> getWeatherForLargeUsCapitals() {return cityDirectory.getUsCapitals() .flatMap(cityList -> Observable.from(cityList)) .filter(city -> city.getPopulation() > 500,000) .flatMap(city -> weatherService.getCurrentWeather(city)) //each runs in parallel .toSortedList((cw1,cw2) -> cw1.getCityName().compare(cw2.getCityName()));}
Cache at rotation (or other cases)
Since this is a loaded data, we may need to cache the data so that when we rotate the device, it does not trigger an event that calls all of the Web service again. One way to do this is to keep the fragment and save a reference to the observable cache, as follows:
@Overridepublic void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); weatherObservable = weatherManager.getWeatherForLargeUsCapitals().cache();}public void onViewCreated(...) { super.onViewCreated(...) bind(weatherObservable).subscribe(this);}
After the rotation, the subscriber's cache instance immediately makes and requests the same request for the first time, preventing the real Web service request from occurring.
If you want to avoid caching fragment (and there are plenty of reasons to avoid it), we can implement caching by using Asyncsubject. Asyncsubject will re-issue the final item whenever it is subscribed. Or we can use Behaviorsubject to get the last value and the new value to change the entire application.
Weatherlistfragment.java
public void onViewCreated() {super.onViewCreated()bind(weatherManager.getWeatherForLargeUsCapitals()).subscribe(this);}
Weathermanager.java
public Observable<List<CityWeather>> getWeatherForLargeUsCapitals() {if (weatherSubject == null) { weatherSubject = AsyncSubject.create(); cityDirectory.getUsCapitals() .flatMap(cityList -> Observable.from(cityList)) .filter(city -> city.getPopulation() > 500,000) .flatMap(city -> weatherService.getCurrentWeather(city)) .toSortedList((cw1,cw2) -> cw1.getCityName().compare(cw2.getCityName())) .subscribe(weatherSubject);}return weatherSubject;}
Because the "cache" is managed separately by the manager, it is not bound to the fragment/activity cycle and will persist in activity/fragment. If you want to force a refresh based on a similar way to preserve the life cycle events of a fragment cache instance, you can do this:
public void onCreate() {super.onCreate()if (savedInstanceState == null) { weatherManager.invalidate(); //invalidate cache on fresh start}}
The great thing about this is that it's not like loaders, and we can be flexible and slow to cache a lot of activity and services results. Just remove the invalidate () call from OnCreate () and let your manager object decide when to emit new weather data. It might be a timer, or a user location change, or any other moment, it really doesn't matter. You can now control when and how to update the cache and reload. And when your cache strategy changes, the interface between fragment and your manager object does not need to be changed. It's just a list of observer.
Testability
Testing is the last challenge we want to make clean and simple. (Let's ignore the fact that during testing we might want to simulate the actual Web service.) It's easy to do this by injecting an interface into a standard pattern that relies on what you might already be doing. )
Fortunately, Observables gives us an easy way to turn an asynchronous method into synchronous, and all you have to do is use the Toblocking () method. Let's look at a test example.
List results = getWeatherForLargeUsCapitals().toBlocking().first();assertEquals(12, results.size());
Like this, we don't need to use futures or countdownlatchs to make some fragile operations, such as threading sleep or making our tests complicated, our tests are now simple, clean, and maintainable.
Conclusion
Update: I have created a couple of simple projects to demonstrate Asynctask style and Asynctaskloader style.
RxJava, you deserve to have. We use RX. Observable to replace Asynctask and Asynctaskloader for more powerful and clear code. Using Rxjava observables is fun, and I look forward to being able to present more solutions for Android problems.
Original link
Replace Asynctask and Asynctaskloader with Rx. Observable–rxjava Android Patterns
"Android development experience" replaces Asynctask and Asynctaskloader-rxjava android templates with rxjava.observable