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.
在上一篇中,你已经学到了如火如荼通过ThreadPoolExecutor
类来将一个任务交给一个线程处理。这篇文章将向你展示如何将来自任务中的数据发送给主线程。这将使得你的任务在后台运行,然后将运行的结果发送给主线程,来更新界面元素,比如在界面上显示bitmap。
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.
每个应用都有它自己的主线程,用来控制界面控件,比如view控件。只有运行于主线程内部的控件才能访问同一线程中的其他控件。由于通过线程池来运行的任务不是运行于主线程,因此这些线程池的线程是不能访问UI界面的控件的。为了能昂线程池中的线程执行的结果能够影响主线程使用handler,它是运行在主线程的。
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.
Handler
是安卓系统框架的一个部分,用来管理线程的。一个Handler
对象如果接收到了一个消息,那么就会运行一些代码来处理这个消息。通常,你可以为一个新线程创建一个Handler
,你也可以创建一个Handler
用来连接一个已经存在的线程。不过,你要是将一个Handler
连接到你的主线程,那么Handler
运行的处理消息的代码就运行在主线程中。难道可以为一般的线程也创建对应的Handler
?
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:
在创建线程池的类的构造器中,初始化Handler
对象,而且将这个Handler
对象设置为一个全局变量。再通过Handler
(Looper)构造器将Handler
对象附属到主线程。Handler
(Looper)构造器会使用一个Looper对象,Looper对象是安卓系统中线程管理框架的一个组成部分。当你使用一个Looper实例来初始化一个Handler
对象,那么Handler
对象将与Looper运行在同一个线程中。例如:
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:
在Handler内部,你要重写handleMessage()方法。当Handler接收到来自它附属的线程发来的消息时,系统会自动调用handleMessage方法。一个线程所有的Handler对象将会接收到同一个消息。例如;
/* * 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.
如果想将工作线程运行的数据传递给主线程中的某个对象,可以在任务中存储这个数据数据和这个对象的引用。然后呢,将这个任务还有执行这个任务后的状态码传递给创建handler的对象。在这个对象中,将包含这个状态码以及任务对象的消息发送给handler。由于handler运行在主线程中,因此它可以将任务产生的结果传递给主线程的对象。说的有点糊涂。
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
.
比如,有一个运行在线程中的任务,这个任务会解码一张图片,并将解码后的图片存储在它的父类phototask中。同时,这个任务也存储有状态码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.
phototask还包含着imageview的一个句柄,这个imageview将来是要显示解码后的图片的。尽管解码后的图片和imageview在同一个对象中,你也不能直接在这个对象中就让imageview显示图片,因为这个任务不是运行在主线程中。
Instead, the next step is to send this status to the PhotoTask
object.
下一个步骤就是讲任务执行的状态码发送给phototask对象。
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
:
PhotoTask
维护着图片的引用,也维护者显示这个图片的imageview的引用。他会从PhotoDecodeRunnable
接受一个状态码,再将它传递给创建了线程池和初始化了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
:
PhotoManager
类是PhotoTask
类的成员,PhotoTask
接受状态码,以及phototask的引用。因为解码任务已经完成,因此,PhotoTask
创建一个消息,该消息包含了状态码和任务对象。
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
:
最后,Handler.handleMessage()
检查传入的每一个消息中的状态码。如果状态码是解码完成的状态码的话,任务就结束了。
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); } ... } ... } ... } ... }
核心就是线程发送给主线程的消息包含一个状态码以及一个对象。对象中包含图片,以及主线程中准备显示图片的控件。平时写的代码只是线程给主线程单独发送一个数字,图片是以全局变量保存的,然后让handler处理这个全局变量的图片。