【后台任务】与UI线程通信(7)

概要


前面的教程指导您如何在线程池线程上运行代码,向您展示如何在由其管理的线程上启动任务 ThreadPoolExecutor。本最后一课向您展示了如何将数据从任务发送到在用户界面(UI)线程上运行的对象。此功能允许您的任务执行后台工作,然后将结果移至UI元素(如位图)。

每个应用程序都有自己的特殊线程来运行诸如对象之类的UI View 对象; 这个线程被称为UI线程。只有在UI线程上运行的对象才能访问该线程上的其他对象。因为您在线程池中的线程上运行的任务 未在UI线程上运行,所以他们无法访问UI对象。要将数据从后台线程移动到UI线程,请使用在UI线程Handler上运行的数据。

在UI线程上定义一个处理程序


Handler是Android系统管理线程框架的一部分。甲 Handler对象接收消息并运行的代码来处理消息。通常,您Handler为一个新线程创建一个线程,但您也可以创建一个Handler与现有线程连接的线程。当你连接Handler到你的UI线程时,处理消息的代码在UI线程上运行。

Handler在创建线程池的类的构造函数中 实例化对象,并将该对象存储在全局变量中。通过使用Handler(Looper) 构造函数实例化它,将它连接到UI线程。这个构造函数使用一个Looper对象,这是Android系统线程管理框架的另一部分。当你Handler基于一个特定的Looper实例来实例化一个 实例的时候, Handler它就像在同一个线程上运行Looper。例如

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

在里面Handler,覆盖handleMessage()方法。当Android系统收到它正在管理的线程的新消息时调用此方法; 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;
            ...
        }
    ...
    }
}

下一节将介绍如何告诉Handler移动数据。

将数据从任务移至UI线程


要将数据从后台线程上运行的任务对象移动到UI线程上的对象,请首先将对数据和UI对象的引用存储在任务对象中。接下来,将任务对象和状态码传递给实例化该对象的对象Handler。在这个对象中,发送一个Message包含状态和任务对象给Handler。由于Handler正在UI线程上运行,因此它可以将数据移动到UI对象。

将数据存储在任务对象中

例如,下面是一个Runnable在后台线程上运行的×××Bitmap,它将其解码 并存储在其父对象中PhotoTask。该Runnable还存储状态代码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还包含ImageView显示的句柄Bitmap。即使到引用Bitmap,并ImageView在同一个对象,你不能分配Bitmap到ImageView,因为你目前没有在UI线程上运行。

相反,下一步是将此状态发送给PhotoTask对象。

向对象层次发送状态

PhotoTask是层次结构中的下一个更高级的对象。它保持对解码数据和View将显示数据的对象的引用。它接收一个状态码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);
    }
    ...
}

将数据移动到用户界面

从PhotoTask对象中,PhotoManager对象接收状态码和PhotoTask对象的句柄。因为状态是 TASK_COMPLETE,创建一个Message包含状态和任务对象并将其发送到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;
            ...
        }
        ...
    }

最后,Handler.handleMessage()检查每个传入的状态码Message。如果状态码是 TASK_COMPLETE,则任务完成,并且该PhotoTask对象Message包含a Bitmap和an ImageView。由于 Handler.handleMessage()正在UI线程上运行,因此可以安全地将其Bitmap移至 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);
                    }
                    ...
                }
                ...
            }
            ...
    }
...
}

更多信息


要了解更多关于Android上的多线程操作的信息,请参阅过程和线程概述指南。

示例应用


要尝试本指南中的概念,请下载ThreadSample

Lastest Update:2018.04.17

联系我

QQ:94297366
微信打赏:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ

公众号推荐:

【后台任务】与UI线程通信(7)

猜你喜欢

转载自blog.51cto.com/4789781/2124451