The AsyncTask
At its core, the AsyncTask is an abstract class that you extend and that provides the basic framework for a time-consuming asynchronous task.
The best way to describe the AsyncTask is to call it a working thread sandwich. That is to say, it has three major methods for which you must provide implementation.
- onPreExecute takes place on the main thread and is the first slice of bread. It sets up the task, prepares a loading dialog, and warns the user that something is about to happen.
- doInBackground is the meat of this little task sandwich. This method is guaranteed by Android to run on a separate background thread. This is where the majority of your work takes place.
- onPostExecute will be called once your work is finished (again, on the main thread), and the results produced by the background method will be passed to it. This is the other slice of bread.
That’s the gist of the asynchronous task. There are more-complicated factors that I’ll touch on in just a minute, but this is one of the fundamental building blocks of the Android platform (given that all hard work must be taken off the main thread).
Take a look at one in action, then we’ll go over the specifics of it:
private class ImageDownloader extends AsyncTask<String, Integer, Bitmap>{ protected void onPreExecute(){ //Setup is done here } @Override protected Bitmap doInBackground(String... params) { //TODO Auto-generated method stub try{ URL url = new URL(params[0]); HttpURLConnection httpCon = (HttpURLConnection)url.openConnection(); if(httpCon.getResponseCode() != 200) throw new Exception("Failed to connect"); InputStream is = httpCon.getInputStream(); return BitmapFactory.decodeStream(is); }catch(Exception e){ Log.e("Image","Failed to load image",e); } return null; } protected void onProgressUpdate(Integer... params){ //Update a progress bar here, or ignore it, it's up to you } protected void onPostExecute(Bitmap img){ ImageView iv = (ImageView)findViewById(R.id.remote_image); if(iv!=null && img!=null){ iv.setImageBitmap(img); } } protected void onCancelled(){ } }
That, dear readers, is an asynchronous task that will download an image at the end of any URL and display it for your pleasure (provided you have an image view onscreen with the ID remote_image). Here is how you’d kick off such a task from the onCreate method of your activity.
public void onCreate(Bundle extras){ super.onCreate(extras); setContentView(R.layout.image_layout); id = new ImageDownloader(); id.execute("http://wanderingoak.net/bridge.png"); }
Once you call execute on the ImageDownloader, it will download the image, process it into a bitmap, and display it to the screen. That is, assuming your image_layout.xml file contains an ImageView with the ID remote_image.
How to Make It Work for You
The AsyncTask requires that you specify three generic type arguments (if you’re unsure about Java and generics, do a little Googling before you press on) as you declare your extension of the task.
- The type of parameter that will be passed into the class. In this example AsyncTask code, I’m passing one string that will be the URL, but I could pass several of them. The parameters will always be referenced as an array no matter how many of them you pass in. Notice that I reference the single URL string as params[0].
- The object passed between the doInBackground method (off the main thread) and the onProgressUpdate method (which will be called on the main thread). It doesn’t matter in the example, because I’m not doing any progress updates in this demo, but it’d probably be an integer, which would be either the percentage of completion of the transaction or the number of bytes transferred.
- The object that will be returned by the doInBackground method to be handled by the onPostExecute call. In this little example, it’s the bitmap we set out to download.
Here’s the line in which all three objects are declared:
private class ImageDownloader extends AsyncTask<String, Integer, Bitmap>{
In this example, these are the classes that will be passed to your three major methods.
Onpreexecute
protected void onPreExecute(){ }
onPreExecute is usually when you’ll want to set up a loading dialog or a loading spinner in the corner of the screen (I’ll discuss dialogs in depth later). Remember, onPreExecute is called on the main thread, so don’t touch the file system or network at all in this method.
Doinbackground
protected Bitmap doInBackground(String... params) { }
This is your chance to make as many network connections, file system accesses, or other lengthy operations as you like without holding up the phone. The class of object passed to this method will be determined by the first generic object in your AsyncTask’s class declaration. Although I’m using only one parameter in the code sample, you can actually pass any number of parameters (as long as they derive from the saved class) and you’ll have them at your fingertips when doInBackground is called. Once your long-running task has been completed, you’ll need to return the result at the end of your function. This final value will be passed into another method called back on the main UI thread.
Showing your Progress
There’s another aspect of the AsyncTask that you should be aware of even though I haven’t demonstrated it. From within doInBackground, you can send progress updates to the user interface. doInBackground isn’t on the main thread, so if you’d like to update a progress bar or change the state of something on the screen, you’ll have to get back on the main thread to make the change.
Within the AsyncTask, you can do this during the doInBackground method by calling publishProgress and passing in any number of objects deriving from the second class in the AsyncTask declaration (in the case of this example, an integer). Android will then, on the main thread, call your declared onProgressUpdate method and hand over any classes you passed to publishProgress. Here’s what the method looks like in the AsyncTask example:
protected void onProgressUpdate(Integer... params){ //Update a progress bar here, or ignore it, it's up to you }
As always, be careful when doing UI updates, because if the activity isn’t currently onscreen or has been destroyed, you could run into some trouble.
Onpostexecute
The work has been finished or, in the example, the image has been downloaded. It’s time to update the screen with what I’ve acquired. At the end of doInBackground, if successful, I return a loaded bitmap to the AsyncTask. Now Android will switch to the main thread and call onPostExecute, passing the class I returned at the end of doInBackground. Here’s what the code for that method looks like:
protected void onPostExecute(Bitmap img){ ImageView iv = (ImageView)findViewById(R.id.remote_image); if(iv!=null && img!=null){ iv.setImageBitmap(img); } }
I take the bitmap downloaded from the website, retrieve the image view into which it’s going to be loaded, and set it as that view’s bitmap to be rendered. There’s an error case I haven’t correctly handled here. Take a second to look back at the original code and see if you can spot it.
A Few Important Caveats
Typically, an AsyncTask is started from within an activity. However, you must remember that activities can have short life spans. Recall that, by default, Android destroys and re-creates any activity each time you rotate the screen. Android will also destroy your activity when the user backs out of it. You might reasonably ask, “If I start an AsyncTask from within an activity and then that activity is destroyed, what happens?” You guessed it: very bad things. Trying to draw to an activity that’s already been removed from the screen can cause all manner of havoc (usually in the form of unhandled exceptions).
It’s a good idea to keep track of any AsyncTasks you’ve started, and when the activity’s onDestroy method is called, make sure to call cancel on any lingering AsyncTask.
There are a few cases in which the AsyncTask is perfect for the job:
- Downloading small amounts of data specific to one particular activity
- Loading files from an external storage drive (usually an SD card)
Make sure, basically, that the data you’re moving with the AsyncTask pertains to only one activity, because your task generally shouldn’t span more than one. You can pass it between activities if the screen has been rotated, but this can be tricky.
There are a few cases when it’s not a good idea to use an AsyncTask:
- Any acquired data that may pertain to more than one activity shouldn’t be acquired through an AsyncTask. Both an image that might be shown on more than one screen and a list of messages in a Twitter application, for example, would have relevance outside a single activity.
- Data to be posted to a web service is also a bad idea to put on an AsyncTask for the following reason: Users will want to fire off a post (posting a photo, blog, tweet, or other data) and do something else, rather than waiting for a progress bar to clear. By using an AsyncTask, you’re forcing them to wait around for the posting activity to finish.
- Last, be aware that there is some overhead for the system in setting up the AsyncTask. This is fine if you use a few of them, but it may start to slow down your main thread if you’re firing off hundreds of them.
You might be curious as to exactly what you should use in these cases. I’m glad you are, because that’s exactly what I’d like to show you next.