Chapter 3. Exploring the AsyncTask
In Chapter 2, Performing Work with Looper, Handler and HandlerThread, we familiarized ourselves with the most basic asynchronous and concurrency constructs available on the Android platform: Handler
and Looper
. Those constructs underpin most of the evented and sequential processing used by the main thread to render the UI and to run the Android components life cycle.
In this chapter, we are going to explore android.os.AsyncTask
, a higher level construct that provides us with a neat and lean interface to perform background work and publish results back to the main thread without having to manage the thread creation and the handler manipulation.
In this chapter we will cover the following topics:
Introducing AsyncTask
Declaring AsyncTask types
Executing AsyncTasks
Providing indeterministic progress feedback
Providing deterministic progress feedback
Canceling an AsyncTask
Handling exceptions
Controlling the level of concurrency
Common AsyncTask issues
Applications of...
AsyncTask
was introduced on the Android platform with Android Cupcake (API Level 3), with the express purpose of helping developers to avoid blocking the main thread. The Async part of the name of this class comes from the word asynchronous, which literally means that the blocking task is not occurring at the same time we call it.
The AsyncTask
encloses the creation of the background thread, the synchronization with the main thread, and the publishing of the progress of the execution in a single construct.
In contrast to the Handler
and Looper
constructs, the AsyncTask
exempts the developer from the management of low level components, thread creation, and synchronization.
AsyncTask
is an abstract class, and as such, must be subclassed for use. At the minimum, our subclass must provide an implementation for the abstract doInBackground
method, which defines the work that we want to get done off the main thread.
The doInBackground...
Declaring AsyncTask types
AsyncTask
is a generically typed class that exposes three generic type parameters:
In order to use a generic type, we must provide one type argument per type parameter that was declared for the generic type.
Note
The generic type class provides a way to re-use the same generic algorithms for different input types. A generic type could have one or more type parameters.
When we declare an AsyncTask
subclass, we'll specify the types for Params, Progress, and Result; for example, if we want to pass a String
parameter to doInBackground
, report progress as a Float
, and return a Boolean
result, we would declare our AsyncTask
subclass as follows:
If we don't need to pass any parameters, or don't want to report progress, a good type to use for those parameters is java.lang.Void
, which signals our intent clearly, because Void
is an uninstantiable class...
Having implemented doInBackground
and onPostExecute
, we want to get our task running. There are two methods we can use for this, each offering different levels of control over the degree of concurrency with which our tasks are executed. Let's look at the simpler of the two methods first:
The return type is the type of our AsyncTask
subclass, which is simply for convenience so that we can use method chaining to instantiate and start a task in a single line and still record a reference to the instance:
The Params... params
argument is the same Params type we used in our class declaration, because the values we supply to the execute method are later passed to our doInBackground
method as its Params... params arguments. Notice that it is a varargs (variable number of parameters...
Providing indeterministic progress feedback
Having started what we know to be a potentially long-running task, we probably want to let the user know that something is happening. There are a lot of ways of doing this, but a common approach is to present a dialog displaying a relevant message.
A good place to present our dialog is from the onPreExecute()
method of AsyncTask
which executes on the main thread so it is allowed to interact with the user interface.
The modified DownloadImageTask
will need a reference to a Context, so that it can prepare a ProgressDialog
, which it will show and dismiss in onPreExecute()
and onPostExecute()
respectively. As doInBackground()
has not changed, it is not shown in the following code, for brevity:
Providing deterministic progress feedback
Knowing that something is happening is a great relief to our users, but they might be getting impatient and wondering how much longer they need to wait. Let's show them how we're getting on by adding a progress bar to our dialog.
Remember that we aren't allowed to update the user interface directly from doInBackground()
, because we aren't on the main thread. How, then, can we tell the main thread to make these updates for us?
AsyncTask
comes with a handy callback method for this, whose signature we saw at the beginning of the chapter:
We can override onProgressUpdate()
to update the user interface from the main thread, but when does it get called and where does it get its Progress... values
from? The glue between doInBackground()
and onProgressUpdate()
is another of AsyncTask's methods:
To update the user interface with our progress, we simply...
Another nice usability touch we can provide for our users is the ability to cancel a task before it completes—for example, if after starting the execution, the user is no longer interested in the operation result. AsyncTask
provides support for cancellation with the cancel method.
The mayInterruptIfRunning
parameter allows us to specify whether an AsyncTask thread that is in an interruptible state, may actually be interrupted—for example, if our doInBackground code is performing a blocking interruptible function, such as Object.wait()
. When we set the mayInterruptIfRunning
as false
, the AsyncTask won't interrupt the current interruptible blocking operation and the AsyncTask background processing will only finish once the blocking operation terminates.
Note
In well behaved interruptible blocking functions, such as Thread.sleep()
, Thread.join(),
or Object.wait(),
the execution is stopped immediately when the thread...
The callback methods defined by AsyncTask
dictate that we cannot throw checked exceptions, so we must wrap any code that throws checked exceptions with try/catch blocks. Unchecked exceptions that propagate out of AsyncTask
's methods will crash our application, so we must test carefully and handle these if necessary.
For the callback methods that run on the main thread—onPreExecute()
, onProgressUpdate()
, onPostExecute()
, and onCancelled()
—we can catch exceptions in the method and directly update the user interface to alert the user.
Of course, exceptions are likely to arise in our doInBackground()
method too, as this is where the bulk of the work of AsyncTask
is done, but unfortunately, we can't update the user interface from doInBackground()
. A simple solution is to have doInBackground()
return an object that may contain either the result or an exception. First we are going to create a generic class for storing the result of an operation and a member to store an exception...
Controlling the level of concurrency
So far, we've carefully avoided being too specific about what exactly happens when we invoke the AsyncTask
execute method. We know that doInBackground()
will execute off the main thread, but what exactly does that mean?
The original goal of AsyncTask
was created to help developers avoid blocking the main thread. In its initial form at API level 3, AsyncTask
s were queued and executed serially (that is, one after the other) on a single background thread, guaranteeing that they would complete in the order they were started.
This changed in API level 4 to use a pool of up to 128 threads to execute multiple AsyncTask
s concurrently with each other—a level of concurrency of up to 128. At first glance, this seems like a good thing, since a common use case for AsyncTask
is to perform blocking I/O, where the thread spends much of its time idly waiting for data.
However, as we saw in Chapter 1, Building Responsive Android Applications, there are many issues that commonly...
As with any powerful programming abstraction, AsyncTask
is not entirely free from issues and compromises. In the next sections we are going to list some of the pitfalls that we could face when we want to make use of this construct in our applications.
In the Controlling the level of concurrency section, we saw how AsyncTask
has evolved with new releases of the Android platform, resulting in behavior that varies with the platform of the device running the task, which is a part of the wider issue of fragmentation.
The simple fact is that if we target a broad range of API levels, the execution characteristics of our AsyncTask
s—and therefore, the behavior of our apps— can vary considerably on different devices. So what can we do to reduce the likelihood of encountering AsyncTask issues due to fragmentation?
The most obvious approach is to deliberately target devices running at least Honeycomb, by setting a minSdkVersion
of 11 in the Android Manifest file...
Applications of AsyncTask
Now that we have seen how to use AsyncTask
, we might ask ourselves when we should use it.
Good candidate applications for AsyncTask
tend to be relatively short-lived operations (at most, for a second or two), which pertain directly to a specific Fragment
or Activity
and need to update its user interface.
AsyncTask
is ideal for running short, CPU-intensive tasks, such as number crunching or searching for words in large text strings, moving them off the main thread so that it can remain responsive to input and maintain high frame rates.
Blocking I/O operations such as reading and writing text files, or loading images from local files with BitmapFactory
are also good use cases for AsyncTask
.
Of course, there are use cases for which AsyncTask
is not ideally suited. For anything that requires more than a second or two, we should weigh the cost of performing this operation repeatedly if the user rotates the device, or switches between apps or activities, or whatever else...
In this chapter, we've taken a detailed look at AsyncTask
and how to use it to write responsive applications that perform operations without blocking the main thread.
We saw how to keep users informed of the progress, and even allow them to cancel operations early. We also learned how to deal with issues that can arise when the Activity lifecycle conspires against our background tasks.
Finally, we considered when to use AsyncTask
, and when it might not be appropriate.
In the next chapter we'll learn about Loader—a construct designed to streamline the asynchronous loading of data on the Android platform.