Replace AsyncTask and AsyncTaskLoader with rx.Observable – RxJava Android Patterns

There are plenty of “Getting Started” type posts talking about RxJava, some of which are in the context of Android. But at this point, I imagine there are people out there who like what they hear so far but aren’t really sure how or why to to use it when solving concrete problems in their Android projects. In this series of posts, I’d like to explore some patterns that have emerged as I’ve worked on various projects that rely on RxJava as the primary backbone of the architecture.

To get this journey started, I’m going to tackle some of the most common pain points that Android developers experience and the easy wins that can be expected when using RxJava. From there, I’ll move on to more advanced and/or niche solutions. During this series of posts, I’d love to hear from other devs out there who are solving similar problems using RxJava and how they may differ or match what I’ve discovered.

Problem 1: Background Tasks

One of the first challenges Android developers face is how to effectively do work on a background thread and come back to the main thread to update the UI. This most often arises from the need to fetch data from a web service. Some of you who have been doing this for some time may be saying, “What’s challenging about that? You just fire up an AsyncTask and it does all of the hard work for you.” And if you’re saying this, there’s a good chance it’s because you’ve either (a) gotten used to doing things in an overly complex way and forgot that it could/should be cleaner, or (b) you aren’t handling all of the edge cases that you should be. Let’s talk a little more about this.

Default Solution: AsyncTask

AsyncTaskis Android’s default tool for developers needing to do some simple long-ish running work in the context of a UI screen without blocking the UI.(NOTE: More recently,AsyncTaskLoaderhas come along to help with the more specific task of loading data. We’ll talk more about this later.)

On the surface, it seems nice and simple. You define some code to run on a background thread, you define some other code to run on the UI thread when it’s done, and it handles passing your data to and from each thread.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 problem with AsyncTask is that the devil is in the details. Let’s talk through some of these.

Error handling

The first problem that arises from this simple usage is: “What happens if something goes wrong?” Unfortunately, there’s no out-of-the-box solution for this, so what a lot of developers end up doing is subclassing AsyncTask, wrapping thedoInBackground()work in a try/catch block, returning a pair of<TResult, Exception>and dispatching to newly defined methods like onSuccess() and onError() based on what happened.(I’ve also seen implementations that just capture a reference to the exception and check it inonPostExcecute().)

This ends up helping a good bit, but now you’re having to write or import extra code for every project you work on, this custom code tends to drift over time, and it’s probably not consistent and predictable from developer to developer and from project to project.

Activity/Fragment lifecycle

Another problem you face is: “What happens if I back out of the Activity or rotate the device while this AsyncTask is running?” Well, if you’re just sending off some fire-and-forget type of work then you might be ok, but what if you are updating the UI based on the result of that task? If you do nothing to prevent it, you will get aNullPointerExceptionand a resulting crash when trying to access the Activity and/or the views since they are now gone and null.

Again, out-of-the-box, AsyncTask doesn’t do much to help you here. As the developer, you need to make sure to keep a reference to the task and either cancel it when the Activity is being destroyed or ensure your Activity is in a valid state before attempting to update the UI inside ofonPostExecute(). This continues to add noise when you just want to get some work done in a clear, easily maintainable way.

Caching on rotation (or otherwise)

What if your user is staying on the same Activity, but just rotating the device? Canceling it doesn’t necessarily make sense in this case because you may end up having to start the task over again after rotation. Or you may not want to restart it because it mutates some state somewhere in a non-idempotent way, but you do want the result so you can update UI to reflect it.

If you are specifically doing a read-only load operation, you could use anAsyncTaskLoaderto solve this problem. But in standard Android fashion, it still brings along with it a ton of boilerplate, lack of error handling, no caching across Activities, and more quirks of its own.

Composing multiple web service calls

Now let’s say we’ve managed to get all of that figured out and working ok, but we now need to make a few network calls back-to-back, each based on the result of the previous call. Or, we might want to make a few network calls in parallel to improve performance and then merge the results together before sending them back to the UI? Sorry to say that once again, AsyncTask will not help you here.

Once you start to do things like this, the previous issues start to pale in comparison to the complexity and pain that starts to grow with coordinating a more complex threading model. 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.

To run them in parallel, you will have to create a custom executor to pass around sinceAsyncTasksdo notrun in parallel by default. And to coordinate parallel threads, you’ll need to dip down into the more complex synchronization patterns using things like CountDownLatchs, Threads, Executors and Futures.

Testability

To top this all off, if you like to unit test your code, and I hope you do, AsyncTask will again not do you any favors. Testing an AsyncTask is difficult without doing something unnatural that’s most likely fragile and/or hard to maintain.Here’s a posttalking about some ways to acheive it successfully.

Better Solution: RxJava’s Observable

Luckily, all the issues we’ve discussed have an elegant solution once we’ve decided to pull in theRxAndroidlibrary. Let’s see what it can do for us.

Now we will write the equivalent code of the AsyncTask we discussed above using Observables instead.(If you use Retrofit, and you should, then it supports the Observable return type and does its work in a background thread pool requiring no additional work on your part.)

1 2 3 4 5 6 webService . doSomething ( someData ) . observeOn ( AndroidSchedulers . mainThread ( ) ) . subscribe ( result -> resultText . setText ( "It worked!" ) ) , e -> handleError ( e ) ) ;

Error handling

You’ll probably notice that, with no additional work, we are already handling both the success and error cases that the AsyncTask does not handle. And we’ve written a lot less code. The extra component you see there is declaring that we want the Observer to handle the results on Android’s main thread. This will change a bit as we go forward. And if yourwebServiceobject doesn’t declare the work to run on a background thread, you could declare that here as well using.subscribeOn(...)(Note that these examples are assuming the use of Java 8’s lambda syntax. This can be achieved in Android projects usingRetrolambda. It’s my current opinion that the reward is higher than the risk, and as of this writing we prefer to use it in our projects.)

Activity/Fragment lifecycle

Now, we will want to utilizeRxAndroidhere to solve the lifecycle problems that we’ve mentioned above and get rid of the need to specify themainThread()scheduler.(You only need to import RxAndroid btw).Doing so will look like this:

1 2 3 4 5 AppObservable . bindFragment ( this , webService . doSomething ( someData ) ) . subscribe ( result -> resultText . setText ( "It worked!" ) ) , e -> handleError ( e ) ) ;

What I typically do is create a helper method in my application base Fragment to simplify this a bit. You can see the base RxFragment I’ve been maintaininghereto give you an idea.

1 2 3 4 5 bind ( webService . doSomething ( someData ) ) . subscribe ( result -> resultText . setText ( "It worked!" ) ) , e -> handleError ( e ) ) ;

AppObservable.bindFragment()inserts a shim into the call chain that preventsonNext()calls from running if our Fragment/Activity is no longer in a valid state. If itisin an invalid state when it tries to run, the subscription will get unsubscribed and execution will stop so that there’s no risk of aNPEand the resulting crash. One thing to note here is that when we leave this Fragment/Activity, we will leak it either temporarily or permanently, depending on the behavior of the Observable in question. So inside thebind()method, I will also hook into theLifeCycleObservablemechanism to have it auto-unsubscribe when the Fragment is destroyed. The good thing is you can take care of this once and never really think about it again.

So, this takes care of the first two main issues. But the next one is when RxJavareallystarts to shine.

Composing multiple web service calls

I won’t go into great detail here becauseit is a deep topic, but by using Observables, you can do very complex things in a simple, easy-to-understand manner. Here’s an example of chaining web service calls that depend on each other, running the second batch of calls in parallel in a thread pool and then merging and sorting the results before sending them back to the Observer. I even threw a filter in there for good measure. All of this logic and coordination is declared in literally five lines of code. Let that sink in for a bit…

1 2 3 4 5 6 7 8 9 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 ( ) ) ) ; }

Caching on rotation (or otherwise)

Now since this is a “load” of data, we probably want to cache this data so that just rotating the device doesn’t trigger the whole set of web service calls again. One way to accomplish this is to set the Fragment to be retained and store a reference to acacheof the Observable like so:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 @ Override public void onCreate ( Bundle savedInstanceState ) { super . onCreate ( savedInstanceState ) ; setRetainInstance ( true ) ; weatherObservable = weatherManager . getWeatherForLargeUsCapitals ( ) . cache ( ) ; } public void onViewCreated ( . . . ) { super . onViewCreated ( . . . ) bind ( weatherObservable ) . subscribe ( this ) ; }

After rotation, subscribing to the cache instance will immediately emit the same items as the first time and prevent it from actually going to the web service.

If you want to avoid retaining the Fragment (and there are good reasons to avoid it) we could also accomplish caching by making use of anAsyncSubjectwhich will re-emit the last item whenever it’s subscribed to. Or we could use aBehaviorSubjectto get the last value and also new values as things change throughout the application.(I’ll talk more about this in a later post where we use ‘hot’ Observables in a more event bus style.

1 2 3 4 5 6 7 8 ## WeatherListFragment.java ## public void onViewCreated ( ) { super . onViewCreated ( ) bind ( weatherManager . getWeatherForLargeUsCapitals ( ) ) . subscribe ( this ) ; }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ## 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 ; }

Since the “cache” is managed by the Manager singleton, it’s not tied to the Fragment/Activity lifecycle and will persist beyond this Fragment/Activity. If you want to force refreshes of the cache based on lifecycle events in a similar way to the retained fragment example, you could do something like this:

1 2 3 4 5 6 7 8 public void onCreate ( ) { super . onCreate ( ) if ( savedInstanceState == null ) { weatherManager . invalidate ( ) ; //invalidate cache on fresh start } }

The great thing about this is, unlike Loaders, we have the flexibility to cache this result across many Activities and Services if we choose. Just remove theinvalidate()call inonCreate()and let your Manager object decide when to emit new weather data. Possibly on a timer, possibly when the user changes location, possibly ____. It really doesn’t matter. You now have control over when and how to invalidate the cache and reload. And the interface between your Fragments and your Manager objects doesn’t change when your caching strategy changes. It’s always just an Observer of aList<WeatherData>.

Testability

Testing is the final piece of the puzzle we want to ensure is clean and simple.(Let’s ignore the fact that we probably want to mock out the actual web services during this test. Doing this is as simple as following the standard pattern of injecting those dependencies via an interface as you mayalready be doing.)

Luckily, Observables give us a simple way to turn an async method into a synchronous one. All you have to do is use the.toBlocking()method. Let’s look at an example test for our method above.

1 2 List results = getWeatherForLargeUsCapitals ( ) . toBlocking ( ) . first ( ) ; assertEquals ( 12 , results . size ( ) ) ;

That’s it. We don’t have to do fragile things like sleeping the thread, or complicate our test by using Futures or CountDownLatchs. Our test is now simple, clear and maintainable.

Conclusion

There you have it. We just replaced the functionality of both AsyncTask and AsyncTaskLoader using the rx.Observable interface while achieving both more power and more clarity in the code that we write. Happy Rx-ing and I look forward to presenting more solutions to common Android problems using RxJava Observables.

更多相关文章

  1. 代码中设置drawableleft
  2. android 3.0 隐藏 系统标题栏
  3. Android开发中activity切换动画的实现
  4. Android(安卓)学习 笔记_05. 文件下载
  5. Android中直播视频技术探究之—摄像头Camera视频源数据采集解析
  6. 技术博客汇总
  7. android 2.3 wifi (一)
  8. AndRoid Notification的清空和修改
  9. Android中的Chronometer

随机推荐

  1. android 制作9.png图片
  2. Maven系列 1. Maven入门
  3. 改用 Android(安卓)之后 II
  4. Android中实现对/system/bin/surfaceflin
  5. 《深入探索Android热修复原理》代码热修
  6. Android——layout_marginStart和layout_
  7. Android(安卓)Studio中使用.9(Nine Patch)
  8. android简单的颜色选择器制作
  9. Android(安卓)增强版百分比布局库 Eclips
  10. Android(安卓)Ap 开发 设计模式第三篇:模