Wednesday, June 24, 2015

Android AsyncTask Example

Often times, you will need to run an operation in the background of your Android app, so that you're not blocking the UI thread. For short running operations, AsyncTask is the perfect tool for the job. The best part is that it's very easy to use.

Let's consider an app that takes an image and adjusts the contrast of the image to make it brighter. By clicking a button in the app, a predefined image is made brighter and then displayed on the screen. To implement the image contrast functionality, let's pretend that we've already written a Java class called ImageUtils that contains a method called setContrast. The signature for this method looks like this:

public Bitmap setContrast(Bitmap image, double contrastValue);

Naively, we could call this method within our button's onClickListener. However, if we do that, then while the image is being manipulated, we will block the UI thread and cause the application to "freeze". So, we can use AsyncTask to perform this image manipulation in the background!

Here's a naive approach to processing the image, within the button's onClickListener:

public void onClickListener(View target) {
 // get the Android icon bitmap that's included in this project 
 Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);

 ImageUtils imageConverter = new ImageUtils();

 Bitmap result = imageConverter.setContrast(originalBitmap, CONTRAST_VALUE);

 ivAndroidIcon.setImageBitmap(result);
}

Thankfully, to use AsyncTask, all that's needed is for us to create a new Java class that extends AsyncTask. Within this class, we'll call our setContrast method. In a sense, we're simply wrapping our current image logic with a class that extends AsyncTask. Pretty simple indeed! Here's how this new class looks:

class BrightenImageTask extends AsyncTask<Bitmap, Void, Bitmap> {
 @Override
 protected Bitmap doInBackground(Bitmap... params) {
  // create a new image that's much brighter than the original image
  ImageUtils imageConverter = new ImageUtils();

  return imageConverter.setContrast(params[0], CONTRAST_VALUE);
 }
  
 @Override
 protected void onPostExecute(Bitmap result) {
  // display the updated image
  ivAndroidIcon.setImageBitmap(result);
 }
}

Now that we have our image processing logic inside an AsyncTask, we can modify our button's onClickListener to call our AsyncTask:

public void onClickListener(View target) {
 // get the Android icon bitmap that's included in this project 
 Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);

 new BrightenImageTask().execute(originalBitmap);
}

There are a couple things to note about AsyncTask:
  1. The generic types that are defined on our AsyncTask class are specifying that a Bitmap is passed into the doInBackground method and a Bitmap is returned by our onPostExecute method.
  2. The VOID type specifies the type that's used to as the unit of progress for the background operation. I didn't use a "progress indicator" in this example, but this is where you would do this type of indication. You could specify this as an Integer and then override the method onProgressUpdate(Integer... progress) to set a progress indicator.
If you really want to get an appreciation for AsyncTask, you can add a Thread.sleep(5000) inside your doInBackground method. You'll notice that you can still click around on the screen while the operation is happening in the background. If instead, you put the image processing logic directly inside the button's onClickListener, you will find that the screen becomes non-responsive while the image is being processed (namely that the button remains depressed and it's obvious that the UI is blocked).

So, next time you need to quickly make an operation run in the background, consider using AsyncTask. Note that this works great for operations that only take a few seconds to complete. However, if you have long running operations, you should consider using an Android service. This is because AsyncTask processes are still connected to the activity stack that executed the AsyncTask. As all of us Android developers know, the Android OS could decide to kill your activity at any time, if the OS needs to recover system resources. This could leave your background task in an unknown state. If you have a long running process, you'll want to use a service that continues to run, just in case your activity is shut down for some unplanned reason.

For reference, I've placed all of the source code for this application on GitHub: https://github.com/travisdazell/asynctask-example

No comments:

Post a Comment