Async Android

RxJava is increasingly popular in the Android developer community. I was thinking back to the “ah ha!” moment when it’s usefulness really struck me. Even before getting to grips with RxJava’s functional aspects, it looked like a neat alternative to AsyncTask and Loader.

I’m going to explore Android’s threading abstractions and explain why RxJava can provide a more satisfying API for expressing asynchronous operations.

In the beginning

Let’s go right back to basics. All Android apps start with a single thread: the main thread (A.K.A “UI thread”). This is the thread we must use to make View updates, and it’s where we receive framework callbacks like onCreate().

Let the main thread do it’s thing

Executing long-running operations on the main thread is bad for performance and bad for users. It means dropped frames, janky animations and unresponsive interfaces. In the very worst cases, the system will show the user an ANR (Application Not Responding) dialog and ask if it should kill the app.

anr

We want to keep the main thread free to make UI updates as close to 60FPS as possible. This means that any slow or blocking operations like network access, database queries or intensive computations must be performed on background threads. The good news is that we’ve got options.

Thread

Creating a new thread is straightforward. Thread is the building block for every approach, but it’s not going to provide a great developer experience on it’s own.

It’s often necessary to react to the result of a background operation on the main thread. A result can be sent to the main thread with a Handler. In the example, Activity.runOnUiThread() simply posts to a main thread Handler internally. Either way, it’s not particularly elegant code.

thread

Creating a new thread for every single background operation isn’t ideal behaviour either. One option is to create a custom abstraction, perhaps sending a Runnable to a thread pool, but there are several more general-purpose tools available.

IntentService

IntentService is perfect for firing off background tasks when it’s not necessary to react to a result on the main thread directly.

It must be declared in the AndroidManifest.xml just like any other Service. Then it can then be started by sending an Intent (that can include any parameters).

startService(new Intent(this, BackgroundService.class));

An IntentService has it’s own worker thread. It operates serially, queuing subsequent invocations. It’s completely decoupled from the app UI, so there are none of the complexities that arise from an Activity being recreated (e.g. due to a configuration change) when a background operation is in progress.

On the other hand, this approach is not particularly well suited when a main thread update is required. It is possible to post a result to an event bus and listen from the UI, but that can become unwieldy as a general solution.

AsyncTask

Painless threading? Well, AsyncTask does have some shortcomings, but it’s the first of several abstractions with a clean separation between the code that runs in the background and the code that handles the result on the main thread.

The interface provides two important callbacks:

  • doInBackground() is executed on a worker thread
  • onPostExecute() receives the result on the main thread

It’s also possible to override onProgressUpdate() and receive progress events on the main thread.

AsyncTask works just fine in simple cases. So what’s the problem? We already touched on handling configuration changes, but that’s a common challenge for threading abstractions that live in the context of an Activity. There are three more concrete limitations.

Error handling

Operations that need to be executed on background threads tend to have a lot of failure cases – especially I/O. Network requests fail. Common patterns involve returning exceptions in result objects or storing them as state in the task. This means that we always need to check for error results (or null) and might end up dealing with less meaningful types.

Callback hell

Chaining multiple asynchronous operations together is complex and error-prone. Scenarios where multiple individual tasks depend on each other are not uncommon, though.

callbackhellChained or nested callbacks lead to “callback hell”. It becomes difficult to reason about the flow of control, and the possible failure of the whole chain must be handled in each callback.

Execution order

pools

This is just a piece of history, assuming a minimum API level 14+. The default threading behaviour of AsyncTask changed twice between platform versions. This resulted in some subtle bugs, inconsistencies and confused developers. It can be specified manually with executeOnExecutor().

Loader

Loader is an evolution of AsyncTask in many ways, and it shares some of the same limitations.

The key difference is that a Loader binds to a host Activity or Fragment. It allows background operations to continue and be restored relatively seamlessly across configuration changes. This is great. It solves a common and surprisingly difficult problem. However, being tightly bound to the framework often provides a separate set of challenges for unit testing.

There are cases where this might be the right tool for the job. For example, CursorLoader does quite a lot for free in combination with ContentProvider. I used it when I was building Sudoku, and it worked well, but that’s a relatively simple application.

In an ideal world

Thread, IntentService, AsyncTask and Loader all provide mechanisms for running code in background threads. They have some distinct characteristics and some shared limitations. So what would an ideal, general-purpose solution for expressing asynchronous operations on Android look like?

  • Main thread callbacks
  • Persistence across configuration changes
  • First class error handling
  • Composability (a solution to callback hell)
  • Simple, explicit control over threading behaviour

RxJava

RxJava is a library for the Observable abstraction and related higher-order functions. It allows us to meet 4/5 of the criteria above with little effort and it supports the functional-reactive programming paradigm in Java.

At this point, it’s fairly well battle-tested on Android. It’s been in production in the SoundCloud app for over two years and it’s used by several other companies like Trello, Square and The New York Times.

Observable

An Observable emits items. Any operation can be represented as a stream that:

  1. emits 0 or more items
  2. either completes or terminates with an error

Marble diagrams help to visualise the output of a stream over time.

create

Subscriber

A Subscriber consumes the output of an Observable. Subscribing to an Observable is essentially registering to receive items emitted from that stream.

  • onNext() receives items emitted by the Obserbable
  • onError() is a terminal event that receives a Throwable
  • onCompleted() indicates that the stream completed successfully

In addition to a separation of concerns between background and main thread work, there is a clean split between logic for handling success and failure cases.

Schedulers

Two important operators define the threading behaviour. In this example, the Observable should execute on a background thread, so it’s assigned to the I/O thread pool with subscribeOn(). The Subscriber callbacks update the UI, so they need to run on the main thread. This is defined with observeOn() and the Android main thread scheduler.

Composing streams

What about “callback hell”? This is where the real power of functional-reactive programming comes into play. RxJava provides an extensive set of operators for functional composition and transformation of streams. Multiple asynchronous operations can be combined with operators like flatMap().

flatmap

In general, these operators allow us to write more expressive, less stateful code. Many are familiar if you have exposure to functional programming.

Problem solved?

Kinda! We’ve seen how the Observable abstraction incorporates error handling, composability and scheduling (including the Android main thread scheduler).

comparison

However, the emoji-based feature comparison shows that RxJava (RxAndroid really) doesn’t provide an out-of-the-box solution for the configuration change problem. Operators like cache() and replay() can play a part, but the strategy for retaining a reference to an Observable will depend on individual architectures.

Some problems solved, some new problems

I want to share a few of the downsides and challenges, because nothing is perfect. These are the topics that seem to come up most often in conversation.

There is a learning curve. Incorporating a new programming paradigm requires you to think about problems in different ways. It can take some time to get familiar with the many useful (or sometimes just very complex) operators.

groupjoinThere is no “correct” way to use RxJava. It can solve a few different problems, so it’s worth reflecting on how it fits into your own architecture and problem-set. A threading abstraction? A tool for representing logic in streams? An event bus? UI bindings?

At some point, you may encounter a very Rx-specific issue called “backpressure”. This occurs when a stream is emitting more items than the subscriber can process. It’s necessary to be mindful of this constraint when using the main thread scheduler, since there is some thread switching overhead.

Currently, Android has no official support for lambda expressions, so function definitions involve a lot of boilerplate. The Retrolambda project backports Java 8 lambdas with Android support.

Conclusion

The default abstractions for expressing asynchronous operations on Android have several limitations in complex scenarios. RxJava provides a full-featured alternative with all the additional power of functional-reactive programming and a simple interface for handling results and errors. Replacing an old AsyncTask is an easy way to get started.


Tools
  • StrictMode: an essential tool for identifying long-running operations on the main thread
  • @UiThread / @WorkerThread: support annotations for static analysis of threading bugs
Links