Android Tutorial: Asynchronous Tasks

This article talks about asynchronous tasks and explains how to handle them using the AsyncTask class. It also presents a photo editor application that illustrates how this class should be used.

Overview

The android.os.AsyncTask class is a utility class that makes it easy to handle background processes and publish progress updates on the UI thread. This class is meant for short operations that last at most a few seconds. For long-running background tasks, you should use the Java Concurrency Utilities framework. Previous article: Android Tutorial: Handling the Handler

The AsyncTask class comes with a set of public methods and a set of protected methods. The public methods are for executing and canceling its task. The execute method starts an asynchronous operation and cancel cancels it. The protected methods are for you to override in a subclass. The doInBackground method, a protected method, is the most important method in this class and provides the logic for the asynchronous operation.

There is also a publishProgress method, also a protected method, which is normally called multiple times from doInBackground. Typically, you will write code to update a progress bar or some other UI component here.

Then there are two onCancelled methods for you to write what should happen if the operation was canceled (i.e. if the AsyncTask’s cancel method was called).

Example

As an example, the PhotoEditor application that accompanies this book uses the AsyncTask class to perform image operations that each takes a few seconds. AsyncTask is used so as not to jam the UI thread. Two image operations, invert and blur, are supported.

The application manifest (the AndroidManifest.xml file) is printed in Listing 1.1.

Listing 1.1: The manifest for PhotoEditor

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
    package="com.example.photoeditor" 
    android:versionCode="1" 

    android:versionName="1.0" > 

    <uses-sdk 
        android:minSdkVersion="8" 
        android:targetSdkVersion="17" /> 

    <application 
        android:allowBackup="true" 
        android:icon="@drawable/ic_launcher" 
        android:label="@string/app_name" 
        android:theme="@style/AppTheme" > 
        <activity 
            android:name="com.example.photoeditor.MainActivity" 
            android:label="@string/app_name" > 
            <intent-filter> 
                <action android:name="android.intent.action.MAIN" /> 
                <category android:name="android.intent.category.LAUNCHER" /> 
            </intent-filter> 
        </activity> 
    </application> 

</manifest>

The layout file, printed in Listing 1.2, shows that the application uses a vertical LinearLayout to house an ImageView, a ProgressBar, and two buttons. The latter are contained in a horizontal LinearLayout. The first button is used to start the blur operation and the second to start the invert operation.

Listing 1.2: The res/layout/activity_main.xml file in PhotoEditor

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:tools="http://schemas.android.com/tools" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:orientation="vertical" 
    android:paddingLeft="16dp" 
    android:paddingRight="16dp" > 

    <LinearLayout 
        android:layout_height="wrap_content" 
        android:layout_width="fill_parent" 
        android:orientation="horizontal" > 

        <Button 
            android:id="@+id/blurButton" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:onClick="doBlur" 
            android:text="@string/blur_button_text" /> 

        <Button 
            android:id="@+id/button2" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:onClick="doInvert" 
            android:text="@string/invert_button_text" /> 
    </LinearLayout> 
    <ProgressBar 
        android:id="@+id/progressBar1" 
        style="?android:attr/progressBarStyleHorizontal" 
        android:layout_width="fill_parent" 
        android:layout_height="10dp" /> 

    <ImageView 
        android:id="@+id/imageView1" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_gravity="top|center" 
        android:src="@drawable/photo1" /> 

</LinearLayout>

Finally, the MainActivity class for this project is given in Listing 1.3.

Listing 1.3: The MainActivity class in PhotoEditor

package com.example.photoeditor;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable; 
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;

public class MainActivity extends Activity { 
    private ProgressBar progressBar; 

    @Override
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
        progressBar = (ProgressBar) findViewById(R.id.progressBar1); 
    }

    @Override 
    public boolean onCreateOptionsMenu(Menu menu) { 
        // Inflate the menu; this adds items to the action bar if it 
        // is present. 
        getMenuInflater().inflate(R.menu.menu_main, menu); 
        return true; 
    } 
     
    public void doBlur(View view) { 
        BlurImageTask task = new BlurImageTask(); 
        ImageView imageView = (ImageView) 
                findViewById(R.id.imageView1); 
        Bitmap bitmap = ((BitmapDrawable)
                imageView.getDrawable()).getBitmap(); 
        task.execute(bitmap); 
    } 
     
    public void doInvert(View view) { 
        InvertImageTask task = new InvertImageTask(); 
        ImageView imageView = (ImageView) 
                findViewById(R.id.imageView1); 
        Bitmap bitmap = ((BitmapDrawable)
        imageView.getDrawable()).getBitmap(); 
        task.execute(bitmap); 
    } 

    private class InvertImageTask extends AsyncTask<Bitmap, Integer, 
            Bitmap> { 
        protected Bitmap doInBackground(Bitmap... bitmap) { 
            Bitmap input = bitmap[0]; 
            Bitmap result = input.copy(input.getConfig(), 
                    /*isMutable'*/true); 
            int width = input.getWidth(); 
            int height = input.getHeight(); 
            for (int i = 0; i < height; i++) { 
                for (int j = 0; j < width; j++) { 
                    int pixel = input.getPixel(j, i); 
                    int a = pixel & 0xff000000; 
                    a = a | (~pixel & 0x00ffffff); 
                    result.setPixel(j, i, a); 
                } 
                int progress = (int) (100*(i+1)/height); 
                publishProgress(progress); 
            } 
            return result; 
        } 

        protected void onProgressUpdate(Integer... values) { 
            progressBar.setProgress(values[0]); 
        } 

        protected void onPostExecute(Bitmap result) { 
            ImageView imageView = (ImageView) 
                    findViewById(R.id.imageView1); 
            imageView.setImageBitmap(result); 
            progressBar.setProgress(0); 
        } 
    } 
    
    private class BlurImageTask extends AsyncTask<Bitmap, Integer, 
            Bitmap> { 
        protected Bitmap doInBackground(Bitmap... bitmap) { 
            Bitmap input = bitmap[0]; 
            Bitmap result = input.copy(input.getConfig(), 
                    /*isMutable=*/ true); 
            int width = bitmap[0].getWidth(); 
            int height = bitmap[0].getHeight(); 
            int level = 7; 
            for (int i = 0; i < height; i++) { 
                for (int j = 0; j < width; j++) { 
                    int pixel = bitmap[0].getPixel(j, i); 
                    int a = pixel & 0xff000000; 
                    int r = (pixel >> 16) & 0xff; 
                    int g = (pixel >> 8) & 0xff; 
                    int b = pixel & 0xff; 
                    r = (r+level)/2; 
                    g = (g+level)/2; 
                    b = (b+level)/2; 
                    int gray = a | (r << 16) | (g << 8) | b; 
                    result.setPixel(j, i, gray); 
                } 
                int progress = (int) (100*(i+1)/height); 
                publishProgress(progress); 
            } 
            return result; 
        } 
        
        protected void onProgressUpdate(Integer... values) { 
            progressBar.setProgress(values[0]); 
        } 

        protected void onPostExecute(Bitmap result) { 
            ImageView imageView = (ImageView) 
                    findViewById(R.id.imageView1); 
            imageView.setImageBitmap(result); 
            progressBar.setProgress(0); 
        } 
    } 
}

The MainActivity class contains two private classes, InvertImageTask and BlurImageTask, which extend AsyncTask. The InvertImageTask task is executed when the Invert button is clicked and the BlurImageTaskwhen the Blur button is clicked.

The doInBackground method in each task processes the ImageView bitmap in a for loop. At each iteration it calls the publishProgress method to update the progress bar.

Figure 1.1 shows the initial bitmap and Figure 1.2 shows the bitmap after an invert operation.

Figure 1.1: The ImageEditor application

Figure 1.2: The bitmap after the invert

Summary

In this article, you learned to use the AsyncTask class and created a photo editor application that uses it.

Leave a Reply

Your email address will not be published.