与UI线程通信

编写:AllenZheng1991 - 原文:http://developer.android.com/training/multiple-threads/communicate-ui.html

在前面的课程中你学习了如何在一个被ThreadPoolExecutor管理的线程中开启一个任务。最后这一节课将会向你展示如何从执行的任务中发送数据给运行在UI线程中的对象。这个功能允许你的任务可以做后台工作,然后把得到的结果数据转移给UI元素使用,例如位图数据。

任何一个APP都有自己特定的一个线程用来运行UI对象,比如View对象,这个线程我们称之为UI线程。只有运行在UI线程中的对象能访问运行在其它线程中的对象。因为你的任务执行的线程来自一个线程池而不是执行在UI线程,所以他们不能访问UI对象。为了把数据从一个后台线程转移到UI线程,需要使用一个运行在UI线程里的Handler

在UI线程中定义一个Handler

Handler属于Android系统的线程管理框架的一部分。一个Handler对象用于接收消息和执行处理消息的代码。一般情况下,如果你为一个新线程创建了一个Handler,你还需要创建一个Handler,让它与一个已经存在的线程关联,用于这两个线程之间的通信。如果你把一个Handler关联到UI线程,处理消息的代码就会在UI线程中执行。

你可以在一个用于创建你的线程池的类的构造方法中实例化一个Handler对象,并把它定义为全局变量,然后通过使用Handler (Looper) 这一构造方法实例化它,用于关联到UI线程。Handler(Looper)这一构造方法需要传入了一个Looper对象,它是Android系统的线程管理框架中的另一部分。当你在一个特定的Looper实例的基础上去实例化一个Handler时,这个HandlerLooper运行在同一个线程里。例如:

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

在这个Handler里需要重写handleMessage()方法。当这个Handler接收到由另外一个线程管理的Handler发送过来的新消息时,Android系统会自动调用这个方法,而所有线程对应的Handler都会收到相同信息。例如:

  1. /*
  2. * handleMessage() defines the operations to perform when
  3. * the Handler receives a new Message to process.
  4. */
  5. @Override
  6. public void handleMessage(Message inputMessage) {
  7. // Gets the image task from the incoming Message object.
  8. PhotoTask photoTask = (PhotoTask) inputMessage.obj;
  9. ...
  10. }
  11. ...
  12. }
  13. }

下一部分将向你展示如何用Handler转移数据。

把数据从一个任务中转移到UI线程

为了从一个运行在后台线程的任务对象中转移数据到UI线程中的一个对象,首先需要存储任务对象中的数据和UI对象的引用;接下来传递任务对象和状态码给实例化Handler的那个对象。在这个对象里,发送一个包含任务对象和状态的MessageHandler也运行在UI线程中,所以它可以把数据转移到UI线程。

在任务对象中存储数据

比如这里有一个Runnable,它运行在一个编码了一个Bitmap且存储这个Bitmap到父类PhotoTask对象里的后台线程。这个Runnable同样也存储了状态码DECODE_STATE_COMPLETED

  1. // A class that decodes photo files into Bitmaps
  2. class PhotoDecodeRunnable implements Runnable {
  3. ...
  4. PhotoDecodeRunnable(PhotoTask downloadTask) {
  5. mPhotoTask = downloadTask;
  6. }
  7. ...
  8. // Gets the downloaded byte array
  9. byte[] imageBuffer = mPhotoTask.getByteBuffer();
  10. ...
  11. // Runs the code for this task
  12. public void run() {
  13. ...
  14. // Tries to decode the image buffer
  15. returnBitmap = BitmapFactory.decodeByteArray(
  16. imageBuffer,
  17. 0,
  18. imageBuffer.length,
  19. bitmapOptions
  20. );
  21. ...
  22. // Sets the ImageView Bitmap
  23. mPhotoTask.setImage(returnBitmap);
  24. // Reports a status of "completed"
  25. mPhotoTask.handleDecodeState(DECODE_STATE_COMPLETED);
  26. ...
  27. }
  28. ...
  29. }
  30. ...

PhotoTask类还包含一个用于显示BitmapImageView的引用。虽然BitmapImageViewImageView的引用在同一个对象中,但你不能把这个Bitmap分配给ImageView去显示,因为它们并没有运行在UI线程中。

这时,下一步应该发送这个状态给PhotoTask对象。

发送状态取决于对象层次

PhotoTask是下一个层次更高的对象,它包含将要展示数据的编码数据和View对象的引用。它会收到一个来自PhotoDecodeRunnable的状态码,并把这个状态码单独传递到一个包含线程池和Handler实例的对象:

  1. public class PhotoTask {
  2. ...
  3. // Gets a handle to the object that creates the thread pools
  4. sPhotoManager = PhotoManager.getInstance();
  5. ...
  6. public void handleDecodeState(int state) {
  7. int outState;
  8. // Converts the decode state to the overall state.
  9. switch(state) {
  10. case PhotoDecodeRunnable.DECODE_STATE_COMPLETED:
  11. outState = PhotoManager.TASK_COMPLETE;
  12. break;
  13. ...
  14. }
  15. ...
  16. // Calls the generalized state method
  17. handleState(outState);
  18. }
  19. ...
  20. // Passes the state to PhotoManager
  21. void handleState(int state) {
  22. /*
  23. * Passes a handle to this task and the
  24. * current state to the class that created
  25. * the thread pools
  26. */
  27. sPhotoManager.handleState(this, state);
  28. }
  29. ...
  30. }

转移数据到UI

PhotoTask对象那里,PhotoManager对象收到了一个状态码和一个PhotoTask对象的引用。因为状态码是TASK_COMPLETE,所以创建一个Message应该包含状态和任务对象,然后把它发送给Handler

  1. public class PhotoManager {
  2. ...
  3. // Handle status messages from tasks
  4. public void handleState(PhotoTask photoTask, int state) {
  5. switch (state) {
  6. ...
  7. // The task finished downloading and decoding the image
  8. case TASK_COMPLETE:
  9. /*
  10. * Creates a message for the Handler
  11. * with the state and the task object
  12. */
  13. Message completeMessage =
  14. mHandler.obtainMessage(state, photoTask);
  15. completeMessage.sendToTarget();
  16. break;
  17. ...
  18. }
  19. ...
  20. }

最终,Handler.handleMessage()会检查每个传入进来的Message,如果状态码是TASK_COMPLETE,这时任务就完成了,而传入的Message里的PhotoTask对象里同时包含一个Bitmap和一个ImageView。因为Handler.handleMessage()运行在UI线程里,所以它能安全地转移Bitmap数据给ImageView

  1. private PhotoManager() {
  2. ...
  3. mHandler = new Handler(Looper.getMainLooper()) {
  4. @Override
  5. public void handleMessage(Message inputMessage) {
  6. // Gets the task from the incoming Message object.
  7. PhotoTask photoTask = (PhotoTask) inputMessage.obj;
  8. // Gets the ImageView for this task
  9. PhotoView localView = photoTask.getPhotoView();
  10. ...
  11. switch (inputMessage.what) {
  12. ...
  13. // The decoding is done
  14. case TASK_COMPLETE:
  15. /*
  16. * Moves the Bitmap from the task
  17. * to the View
  18. */
  19. localView.setImageBitmap(photoTask.getImage());
  20. break;
  21. ...
  22. default:
  23. /*
  24. * Pass along other messages from the UI
  25. */
  26. super.handleMessage(inputMessage);
  27. }
  28. ...
  29. }
  30. ...
  31. }
  32. ...
  33. }
  34. ...
  35. }