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);
                    }
                    ...
                }
                ...
            }
            ...
    }
...
}