Asynchronous Android Programming — the Good, the Bad, and the Ugly
When writing Android code, there are many situations in where you’ll need to make network calls. There are several different ways of performing these network calls, and after reviewing them all, we’ve determined some are good, some are bad, and one in particular is just plain ugly. We’ve compared and contrasted several methods, and along with some code examples, will provide some basic insight into using what we think is the best method of making network calls in an Android application.
The Ugly: Network Calls on the User Interface Thread
The easiest way to make a network call when writing Android native code is to make the call directly on the user interface thread:
Although this might seem to work with short network calls on early versions of Android, this is an ugly way of performing this task. When you run a time-consuming section of code on the user interface thread, it blocks other actions from occurring. To a user, this looks like the application has frozen. If a user performs an action while the user interface thread is blocked, and the application doesn’t respond within 5 seconds, the Android system will show an “Application Not Responding” dialog:
If you attempt to make a network call on more recent versions of Android (Honeycomb and newer) your application will actually crash, as these newer versions are much stricter about misusing the user interface thread. Anything that makes your app crash (or users think it’s crashed) can definitely be called “Ugly”.
The Bad Part 1: Runnables and Handlers
To avoid freezing and crashing of an Android application, network calls need to be performed in the background. One way of doing is by using a Runnable that contains the code you want to run in the background, and running that on a new thread.
This works for performing the task, but if you want to update the user interface afterwards, you can’t do that inside the Runnable, as it is running on a thread other than the user interface thread. Instead, a Handler can be created on the user interface thread, and used inside the Runnable to trigger the Handler’s “handleMessage()” callback.
The main risk of Runnables and Handlers is memory leakage. If the code in the runnable is very time consuming, or you are delaying that Runnable from running, the reference to the Handler holds a reference to whatever activity it was created in. This means that if an Activity creates an Handler, runs a time consuming Runnable in another thread, and navigates away from the Activity while the Runnable is still working away, that reference will prevent the activity from being garbage collected. To avoid this, the Handler needs to be statically created so it doesn’t hold a reference to the activity, and a WeakReference added to the activity so you can call its methods to update the user interface without creating a memory leak. You can use Runnables and Handlers to make a network call in the background and update the user interface using the Handler, but if multiple network calls that update the user interface were made at the same time, there is no guarantee as to which order the Handler callbacks will be received. That, combined with the extra work needed to avoid memory leaks, firmly places Runnables and Handlers in the “Bad” category.
The Bad Part 2: AsyncTasks
Another less-than-ideal way of making a network call in the background is with the use of an AsyncTask. Their basic use is fairly straightforward — make a subclass of an AsyncTask, override the “doInBackground” method, and possibly the “onPreExecute”, “onProgressUpdate” and “onPostExecute” methods.
The “onPreExecute” is run on the user interface thread, and is used to perform any setup necessary before starting the background, such as showing a progress bar. Next, “doInBackground” is called in the background to perform the network call. While that runs, if you call the “publishProgress” method from inside the “doInBackground”, “onProgressUpdate” is called on the user interface thread, allowing you to perform user related tasks like updating a progress bar. Finally, once the background work is done, “onPostExecute” is called on the user interface thread, allowing for actions such as cancelling the progress bar and showing the result of the network call.
This is much better than making the network call on the user interface thread, but there are still issues to avoid: if you want to cancel the AsyncTask, it waits until after the “doInBackground” method finishes. This means that even if you call the cancel method on an async task, it will wait until the background work is finished before cancelling the task, unless you check the result from “isCancelled” over and over again in the “doInBackground” method.
Another issue with AsyncTasks is if you have more than one running at once. You have no guarantee what order they’ll complete in, resulting in complex logic to check when all the tasks have completed. Even worse is the assumption that one will finish before the other, until you hit an edge case that makes the first call slower, which makes them complete in the wrong order and undesired results.
Since AsyncTasks can run partially on the user interface thread and partially on a background thread, they can result in memory leaks. As AsyncTasks often update the user interface in the “onPostExecute” method, it is easy to make the AsyncTask an inner class of the Activity and have access to the user interface elements of the Activity. This creates an implicit reference to the Activity, which prevents the Activity from being garbage collected while the AsyncTask is still running, even if the Activity has been destroyed.
Although AsyncTasks can make network calls in the background, there are too many potential issues associated with their use: AsyncTasks are fit for the pit.
The Good: RxJava
Finally, we get to our favourite method of background network calls: RxJava library, a Java implementation of Reactive Extensions. Using RxJava’s Observables and Subscriptions, it is possible to create an Observer that overrides the “onNext”, “onError” and “onCompleted” methods, and subscribe to a method that returns an Observable. The Observable will emit items back to the Observer, causing the “onNext” method to be called with access to the item emitted. If the Observable is finished emitting objects, “onCompleted” is called. If an exception is thrown at any time, “onError” is called with that exception, allowing for easy error handling.
The Observable also allows for operators to be used and chained to modify the observable. For instance, subscribeOn tells the observable which thread you want the work to be performed on, and observeOn, which is what thread you want your observer to use. There are hundreds of operators that can be used to tweak the observable, such as the map operator, which transforms an emitted item into something else.
Another useful operator is merge, which uses multiple Observables, and emits items from all of the Observables to the same Observer, calling onNext once for every item emitted from each Observable. If an error is received from either Observable during this process, onError is called and no more items are emitted. If this isn’t desired, mergeDelayError can be used instead, which works the same as merge, but if there is an error, it waits until all Observables complete or have errors before triggering “onError”.
Operators can also be combined, allowing for all sorts of modifications on emitted objects. There is no limit to combining operators, making accomplishing complex tasks with RxJava much simpler. One such combination is using the collect operator after the merge operator. This allows us to grab items emitted from any of the merged observables, and put them all into a data structure such as a list.
The only clean up you need to do when using Observables and Subscriptions is make sure to unsubscribe the Subscription in the onDestroy method of your activity. This is a simple way to prevent memory leaks, and makes sense, as it follows the Android lifecycle.
The use of RxJava’s Observables and Subscriptions allows for a high level of control over background network calls. Powerful operators allow you to map the results to change, merge, or join observables, which make the logic around those network calls scalable and maintainable. These are all great things, and why we like RxJava as the best method for making network calls.
- Techotopia — Overview of Threads and Handlers
- Techblog — Android Handler Memory Leaks
- Random — An Android Blog — AsyncTasks
- Dan Lew’s Blog — Grokking RxJava
- The RxJava Wiki
About the Author: Devin Corrigall is a Co-op student on the Android team, entering his third year of Software Engineering at UVic. He enjoys making Android apps, Luge and the outdoors. Follow him on Twitter @devincorrigall.