In the previous lesson you learned how to start a task on a thread managed by
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 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 Handler
that's
running on the UI thread.
Define a Handler on the UI Thread
Handler
is part of the Android system's framework for managing threads. A
Handler
object receives messages and runs code to handle the messages.
Normally, you create a Handler
for a new thread, but you can
also create a Handler
that's connected to an existing thread.
When you connect a Handler
to your UI thread, the code that handles messages
runs on the UI thread.
Instantiate the 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 Handler(Looper)
constructor. This constructor uses a Looper
object, which is another part of
the Android system's thread management framework. When you instantiate a
Handler
based on a particular Looper
instance, the
Handler
runs on the same thread as the Looper
.
For example:
private PhotoManager() { ... // Defines a Handler object that's attached to the UI thread mHandler = new Handler(Looper.getMainLooper()) { ...
Inside the Handler
, override the handleMessage()
method. The Android system invokes this method when it receives a new message
for a thread it's managing; all of the 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 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 Handler
.
In this object, send a Message
containing the status and the task object to
the Handler
. Because 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 Runnable
, running on a background thread, that decodes a
Bitmap
and stores it in its parent object PhotoTask
.
The 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 ImageView
that
displays the Bitmap
. Even though references to
the Bitmap
and ImageView
are in the same object,
you can't assign the Bitmap
to the 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 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 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 Message
containing the state and task
object and sends it to the 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, Handler.handleMessage()
checks the status
code for each incoming Message
. If the status code is
TASK_COMPLETE
, then the task is finished, and the PhotoTask
object
in the Message
contains both a Bitmap
and an
ImageView
. Because
Handler.handleMessage()
is
running on the UI thread, it can safely move the Bitmap
to the
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); } ... } ... } ... } ... }