Android's AsyncTask
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 AsyncTask
s. 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:
Params
is the argument type for the varargs array passed in to doInBackground.Progress
is the argument type for the varargs array passed in toonProgressUpdate
, and so is also the type (of array) you must use when invokingpublishProgress
.Result
is the return type ofdoInBackground
, which in turn is the argument type passed in toonPostExecute
.
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 AsyncTask
s 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.