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
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
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.
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.
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.
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 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));
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.
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.
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.
Chaining multiple asynchronous operations together is complex and error-prone. Scenarios where multiple individual tasks depend on each other are not uncommon, though.
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
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
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
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 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 emits items. Any operation can be represented as a stream that:
- emits 0 or more items
- either completes or terminates with an error
Marble diagrams help to visualise the output of a stream over time.
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
onError()is a terminal event that receives a
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.
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
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.
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
In general, these operators allow us to write more expressive, less stateful code. Many are familiar if you have exposure to functional programming.
Kinda! We’ve seen how the
Observable abstraction incorporates error handling, composability and scheduling (including the Android main thread scheduler).
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
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.
There 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.
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.
- StrictMode: an essential tool for identifying long-running operations on the main thread
- @UiThread / @WorkerThread: support annotations for static analysis of threading bugs