The Android platform allows you to use all of the normal Java concurrency constructs. You should use them if you need to do any long-running* operations: you must do these off the main UI thread if you want keep your users happy, and the platform even enforces this by displaying an Application Not Responding dialog if an app does not respond to user-input within 5 seconds.

The platform provides a couple of mechanisms to facilitate communication between background threads and the main application thread: Handler's and AsyncTasks. In this article I want to concentrate on AsyncTask.

update, December 2013: I came across a StackOverflow post referencing this article, where the poster was - understandably - confused by my use of "long-running" in the first paragraph. Long-running is a relative term, and here I mean 'more than a few milliseconds and less than ~500ms'.

The main thread has just 16.67ms to render a single frame at 60Hz, so if you do anything on the main thread that gets even close to using up that 16ms you're risking skipped frames.

For operations that are long-running in human terms (500ms+) there are other constructs which may be more appropriate (Loaders/Services) - you might find my book useful, which covers all of the Android concurrency constructs in detail, including much more up-to-date and in-depth coverage of AsyncTask.

There are all kinds of things you will want to do off the UI thread, but AsyncTask (when used from an Activity or Fragment) is only really appropriate for relatively short operations. Ideal uses include CPU-intensive tasks, such as number crunching or searching for words in large text strings, blocking I/O operations such as reading and writing text files, or loading images from local files with BitmapFactory.

The basics of AsyncTask

AsyncTask provides a simple API for doing work in the background and re-integrating the result with the main thread. Here's what it looks like:

new AsyncTask<Param, Progress, Result>() {
    protected void onPreExecute() {
        // perhaps show a dialog 
        // with a progress bar
        // to let your users know
        // something is happening
    }

    protected Result doInBackground(Param... aParams) {
        // do some expensive work 
        // in the background here
    }

    protected void onPostExecute(Result aResult) {
        // background work is finished, 
        // we can update the UI here
        // including removing the dialog
    }
}.execute();

The template methods onPreExecute() and onPostExecute(Result) are invoked such that you can safely update the UI from there.

There is a fourth template method - onProgressUpdate(Progress[]) - which you can implement if you want to update the UI to show progress is being made within the background thread. For this to actually work you will need to invoke publishProgress(Progress[]) regularly from within doInBackground(Param[]).

AsyncTask is generic, and presents three type variables:

class AsyncTask<Params, Progress, Result>

They are used as follows:

  1. Params is the argument type for the varargs array passed in to doInBackground.
  2. Progress is the argument type for the varargs array passed in to onProgressUpdate, and so is also the type (of array) you must use when invoking publishProgress.
  3. Result is the return type of doInBackground, which in turn is the argument type passed in to onPostExecute.

What happens when you execute()?

When execute(Object.. params) is invoked on an AsyncTask the task is executed in a background thread. Depending on the platform AsyncTasks may be executed serially (pre 1.6 and potentially again in 4+), or concurrently (1.6-3.2).

To be sure of running serially or concurrently as you require, from API Level 11 onwards you can use the executeOnExecutor(Executor executor, Object.. params) method instead, and supply an executor. The platform provides two executors for convenience, accessable as AsyncTask.SERIAL_EXECUTOR and AsyncTask.THREAD_POOL_EXECUTOR respectively. (Note: If you are targeting earlier API levels executeOnExecutor is not available, but you have several options - see below).

I have not tested exhaustively, but at least on tablets running HoneyComb the THREAD_POOL_EXECUTOR is set up with a maximum pool size of 128 and an additional queue length of 10.

If you exhaust the pool by submitting too many AsyncTask's concurrently you will receive RejectedExecutionException's - a subclass of RuntimeException which, unless handled, will crash your application.

I suspect that on a resource-constrained device it is probably quite a disaster if you actually have that many AsyncTask's active concurrently - context-switching all those threads will render the cpu-cache ineffective, cost a lot in terms of CPU time, and anyway all those concurrently active threads will likely be using a good chunk of your heap and generating garbage for the GC to contend with.

You might want to consider an alternative Executor configured with a lower max threads and a longer queue, or a more appropriate strategy for managing the background work: for example if you have many files to download you could enqueue a url and a callback to a download-manager instead of executing an AsyncTask for each one.

executeOnExecutor for API levels below 11

AsyncTask gained a nice new method at API level 11 - executeOnExecutor - allowing you some control of the concurrency of your AsyncTask's. If you need to support older API levels you have a choice to make: do you absolutely have to have executeOnExecutor, or do you simply want to use it when it is available, and fall-back to execute otherwise?

The fallback approach

If you want a simple way to take some measure of control where possible, you can subclass AsyncTask, test for the API level at runtime, and invoke the executeOnExecutor method if it is available - something like this:

class MyAsyncTask<Param, Progress, Result> {

    private static final boolean API_LEVEL_11 
        = android.os.Build.VERSION.SDK_INT > 11;

    public void execute(Executor aExecutor, Params... aParams) {     
        if(API_LEVEL_11)
            executeOnExecutor(aExecutor, aParams); 
        else
            super.execute(aParams);
    }

}

I know that at first glance something appears wrong here:

private static final boolean API_LEVEL_11 
    = android.os.Build.VERSION.SDK_INT > 11;

This looks like it will be optimised out by the compiler - a static comparison of a literal integer (11) with what appears to be another static integer (android.os.Build.VERSION.SDKINT), but in fact the upper-case VERSION.SDKINT is slightly misleading - the values in VERSION are extracted at runtime from system properties, so the comparison is not baked in at compile-time.

executeOnExecutor for all API levels

If you insist on having executeOnExecutor available for all API levels you might try this: copy the code for AsyncTask from API level 15, rename it (and make a few small changes as described here), and use that everywhere in place of the SDK version.

AsyncTask and the Activity lifecycle

The Activity lifecycle is well defined and provides template methods which are invoked when critical events occur in the life of an Activity.

AsyncTask's are started by an Activity because it needs some potentially blocking work done off the UI thread, and unless you really really know what you are doing they should live and die with that Activity.

If your AsyncTask retains a reference to the Activity, not cancelling the task when the Activity dies wastes CPU resources on work that cannot update its UI, and creates a memory leak (the Activity and all its View hierarchy will be retained until the task completes).

Don't forget that the Activity is destroyed and re-created even on something as simple as a device orientation change, so if a user rotates their device you will have two copies of your Activity retained until the last AsyncTask completes. In a memory constrained environment this can be a disaster!

For longer operations, e.g. networking, consider whether IntentService or Service are more appropriate to your needs (be aware, though, that Service does not automatically offload work from the main thread!).

For some useful discussion and ideas related to AsyncTask and lifecycle management, see this stackoverflow post.

AsyncTask and good Android citizenship

AsyncTask is in the SDK because it fulfils a common need, but it does not enforce a usage pattern that makes your app a good Android citizen.

In an environment where users switch contexts frequently and quickly (example: receive a phone call while in the midst of writing an email on your phone), it is probably important that your app does not hog resources whilst it is not the current focus.

If, as described above, you've set yourself up to cancel tasks according to the Activity lifecycle methods then you're all set and should not face any issues here.

My book, Asynchronous Android, goes into a lot more detail of how to use AsyncTask, including things like showing progress bars, continuing work across Activity restarts, understanding how AsyncTask works, cancelling running tasks, exception handling, and how to avoid some very common issues that developers face.

AsyncTask is just one of the 7 chapters; the book aims to arm you with a complete toolkit for building smooth, responsive apps by working with the platform to get work done off the main thread.

blog comments powered by Disqus