page.title=Communicating with the UI Thread trainingnavtop=true @jd:body

This lesson teaches you to

  1. Define a Handler on the UI Thread
  2. Move Data from a Task to the UI Thread

You should also read

Try it out

Download the sample

ThreadSample.zip

In the previous lesson you learned how to start a task on a thread managed by {@link java.util.concurrent.ThreadPoolExecutor}. This final lesson shows you how to send data from the task to objects running on the user interface (UI) thread. This feature allows your tasks to do background work and then move the results to UI elements such as bitmaps.

Every app has its own special thread that runs UI objects such as {@link android.view.View} objects; this thread is called the UI thread. Only objects running on the UI thread have access to other objects on that thread. Because tasks that you run on a thread from a thread pool aren't running on your UI thread, they don't have access to UI objects. To move data from a background thread to the UI thread, use a {@link android.os.Handler} that's running on the UI thread.

Define a Handler on the UI Thread

{@link android.os.Handler} is part of the Android system's framework for managing threads. A {@link android.os.Handler} object receives messages and runs code to handle the messages. Normally, you create a {@link android.os.Handler} for a new thread, but you can also create a {@link android.os.Handler} that's connected to an existing thread. When you connect a {@link android.os.Handler} to your UI thread, the code that handles messages runs on the UI thread.

Instantiate the {@link android.os.Handler} object in the constructor for the class that creates your thread pools, and store the object in a global variable. Connect it to the UI thread by instantiating it with the {@link android.os.Handler#Handler(Looper) Handler(Looper)} constructor. This constructor uses a {@link android.os.Looper} object, which is another part of the Android system's thread management framework. When you instantiate a {@link android.os.Handler} based on a particular {@link android.os.Looper} instance, the {@link android.os.Handler} runs on the same thread as the {@link android.os.Looper}. For example:

private PhotoManager() {
...
    // Defines a Handler object that's attached to the UI thread
    mHandler = new Handler(Looper.getMainLooper()) {
    ...

Inside the {@link android.os.Handler}, override the {@link android.os.Handler#handleMessage handleMessage()} method. The Android system invokes this method when it receives a new message for a thread it's managing; all of the {@link android.os.Handler} objects for a particular thread receive the same message. For example:

        /*
         * handleMessage() defines the operations to perform when
         * the Handler receives a new Message to process.
         */
        @Override
        public void handleMessage(Message inputMessage) {
            // Gets the image task from the incoming Message object.
            PhotoTask photoTask = (PhotoTask) inputMessage.obj;
            ...
        }
    ...
    }
}
The next section shows how to tell the {@link android.os.Handler} to move data.

Move Data from a Task to the UI Thread

To move data from a task object running on a background thread to an object on the UI thread, start by storing references to the data and the UI object in the task object. Next, pass the task object and a status code to the object that instantiated the {@link android.os.Handler}. In this object, send a {@link android.os.Message} containing the status and the task object to the {@link android.os.Handler}. Because {@link android.os.Handler} is running on the UI thread, it can move the data to the UI object.

Store data in the task object

For example, here's a {@link java.lang.Runnable}, running on a background thread, that decodes a {@link android.graphics.Bitmap} and stores it in its parent object PhotoTask. The {@link java.lang.Runnable} also stores the status code DECODE_STATE_COMPLETED.

// A class that decodes photo files into Bitmaps
class PhotoDecodeRunnable implements Runnable {
    ...
    PhotoDecodeRunnable(PhotoTask downloadTask) {
        mPhotoTask = downloadTask;
    }
    ...
    // Gets the downloaded byte array
    byte[] imageBuffer = mPhotoTask.getByteBuffer();
    ...
    // Runs the code for this task
    public void run() {
        ...
        // Tries to decode the image buffer
        returnBitmap = BitmapFactory.decodeByteArray(
                imageBuffer,
                0,
                imageBuffer.length,
                bitmapOptions
        );
        ...
        // Sets the ImageView Bitmap
        mPhotoTask.setImage(returnBitmap);
        // Reports a status of "completed"
        mPhotoTask.handleDecodeState(DECODE_STATE_COMPLETED);
        ...
    }
    ...
}
...

PhotoTask also contains a handle to the {@link android.widget.ImageView} that displays the {@link android.graphics.Bitmap}. Even though references to the {@link android.graphics.Bitmap} and {@link android.widget.ImageView} are in the same object, you can't assign the {@link android.graphics.Bitmap} to the {@link android.widget.ImageView}, because you're not currently running on the UI thread.

Instead, the next step is to send this status to the PhotoTask object.

Send status up the object hierarchy

PhotoTask is the next higher object in the hierarchy. It maintains references to the decoded data and the {@link android.view.View} object that will show the data. It receives a status code from PhotoDecodeRunnable and passes it along to the object that maintains thread pools and instantiates {@link android.os.Handler}:

public class PhotoTask {
    ...
    // Gets a handle to the object that creates the thread pools
    sPhotoManager = PhotoManager.getInstance();
    ...
    public void handleDecodeState(int state) {
        int outState;
        // Converts the decode state to the overall state.
        switch(state) {
            case PhotoDecodeRunnable.DECODE_STATE_COMPLETED:
                outState = PhotoManager.TASK_COMPLETE;
                break;
            ...
        }
        ...
        // Calls the generalized state method
        handleState(outState);
    }
    ...
    // Passes the state to PhotoManager
    void handleState(int state) {
        /*
         * Passes a handle to this task and the
         * current state to the class that created
         * the thread pools
         */
        sPhotoManager.handleState(this, state);
    }
    ...
}

Move data to the UI

From the PhotoTask object, the PhotoManager object receives a status code and a handle to the PhotoTask object. Because the status is TASK_COMPLETE, creates a {@link android.os.Message} containing the state and task object and sends it to the {@link android.os.Handler}:

public class PhotoManager {
    ...
    // Handle status messages from tasks
    public void handleState(PhotoTask photoTask, int state) {
        switch (state) {
            ...
            // The task finished downloading and decoding the image
            case TASK_COMPLETE:
                /*
                 * Creates a message for the Handler
                 * with the state and the task object
                 */
                Message completeMessage =
                        mHandler.obtainMessage(state, photoTask);
                completeMessage.sendToTarget();
                break;
            ...
        }
        ...
    }

Finally, {@link android.os.Handler#handleMessage Handler.handleMessage()} checks the status code for each incoming {@link android.os.Message}. If the status code is TASK_COMPLETE, then the task is finished, and the PhotoTask object in the {@link android.os.Message} contains both a {@link android.graphics.Bitmap} and an {@link android.widget.ImageView}. Because {@link android.os.Handler#handleMessage Handler.handleMessage()} is running on the UI thread, it can safely move the {@link android.graphics.Bitmap} to the {@link android.widget.ImageView}:

    private PhotoManager() {
        ...
            mHandler = new Handler(Looper.getMainLooper()) {
                @Override
                public void handleMessage(Message inputMessage) {
                    // Gets the task from the incoming Message object.
                    PhotoTask photoTask = (PhotoTask) inputMessage.obj;
                    // Gets the ImageView for this task
                    PhotoView localView = photoTask.getPhotoView();
                    ...
                    switch (inputMessage.what) {
                        ...
                        // The decoding is done
                        case TASK_COMPLETE:
                            /*
                             * Moves the Bitmap from the task
                             * to the View
                             */
                            localView.setImageBitmap(photoTask.getImage());
                            break;
                        ...
                        default:
                            /*
                             * Pass along other messages from the UI
                             */
                            super.handleMessage(inputMessage);
                    }
                    ...
                }
                ...
            }
            ...
    }
...
}