Google 致力于为黑人社区推动种族平等。查看具体举措

与界面线程通信

上一篇指南介绍了如何在线程池线程上运行代码,以及如何在 ThreadPoolExecutor 托管的线程上启动任务。最后一课,我们将为您介绍如何将数据从任务发送到界面 (UI) 线程上运行的对象。此功能允许任务执行后台工作,然后将结果移至位图等界面元素。

每个应用都有自己的特殊线程来运行界面对象(比如 View 对象),该线程称为界面线程。只有在界面线程上运行的对象才能访问该线程上的其他对象。由于在线程池中的线程上运行的任务不在界面线程上运行,因此它们不能访问界面对象。如需将数据从后台线程移动到界面线程,请使用在界面线程上运行的 Handler

在界面线程上定义 Handler

Handler 是 Android 系统线程管理框架的一部分。Handler 对象会接收消息并运行代码来处理消息。 通常情况下,会为新线程创建 Handler,但也可创建连接到现有线程的 Handler。 将 Handler 连接到界面线程时,处理消息的代码会在界面线程上运行。

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

Kotlin

    object PhotoManager {
    ...
        private val handler: Handler = Handler(Looper.getMainLooper())
        ...
    }
    

Java

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

Handler 内,替换 handleMessage() 方法。当 Android 系统收到它所管理的线程的新消息时,就会调用此方法。特定线程的所有 Handler 对象都会收到同一条消息。例如:

Kotlin

    object PhotoManager {
        private val handler: Handler = object : Handler(Looper.getMainLooper()) {
            /*
             * handleMessage() defines the operations to perform when
             * the Handler receives a new Message to process.
             */
            override fun handleMessage(inputMessage: Message) {
                // Gets the image task from the incoming Message object.
                val photoTask = inputMessage.obj as PhotoTask
                ...
            }
        }
        ...
    }
    

Java

            /*
             * 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 移动数据。

将数据从任务移到界面线程

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

将数据存储在任务对象中

例如,这是一个在后台线程上运行的 Runnable,它将对 Bitmap 进行解码并将其存储在其父对象 PhotoTask 中。 Runnable 还会存储状态代码 DECODE_STATE_COMPLETED

Kotlin

    const val DECODE_STATE_COMPLETED: Int = ...

    // A class that decodes photo files into Bitmaps
    class PhotoDecodeRunnable(
            private val photoTask: PhotoTask,
            // Gets the downloaded byte array
            private var imageBuffer: ByteArray = photoTask.getByteBuffer()
    ) : Runnable {
        ...
        // Runs the code for this task
        override fun run() {
            ...
            // Tries to decode the image buffer
            BitmapFactory.decodeByteArray(
                    imageBuffer,
                    0,
                    imageBuffer.size,
                    bitmapOptions
            )?.also { returnBitmap ->
                ...
                // Sets the ImageView Bitmap
                photoTask.image = returnBitmap
            }
            // Reports a status of "completed"
            photoTask.handleDecodeState(DECODE_STATE_COMPLETED)
            ...
        }
        ...
    }
    

Java

    // A class that decodes photo files into Bitmaps
    class PhotoDecodeRunnable implements Runnable {
        ...
        PhotoDecodeRunnable(PhotoTask downloadTask) {
            photoTask = downloadTask;
        }
        ...
        // Gets the downloaded byte array
        byte[] imageBuffer = photoTask.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
            photoTask.setImage(returnBitmap);
            // Reports a status of "completed"
            photoTask.handleDecodeState(DECODE_STATE_COMPLETED);
            ...
        }
        ...
    }
    ...
    

PhotoTask 还包含显示 BitmapImageView 的句柄。即使对 BitmapImageView 的引用位于同一对象中,也不能将 Bitmap 分配给 ImageView,因为您当前不在界面线程上运行。

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

沿着对象层次结构往上发送状态

PhotoTask 是层次结构中的下个更上层对象。该对象中存放着对解码数据以及将显示该数据的 View 对象的引用。它从 PhotoDecodeRunnable 接收状态代码,并将其传递给存放线程池并实例化 Handler 的对象:

Kotlin

    // Gets a handle to the object that creates the thread pools
    class PhotoTask() {
        ...
        private val photoManager: PhotoManager = PhotoManager.getInstance()
        ...
        fun handleDecodeState(state: Int) {
            // Converts the decode state to the overall state.
            val outState: Int = when(state) {
                PhotoDecodeRunnable.DECODE_STATE_COMPLETED -> PhotoManager.TASK_COMPLETE
                ...
            }
            ...
            // Calls the generalized state method
            handleState(outState)
        }
        ...
        // Passes the state to PhotoManager
        private fun handleState(state: Int) {
            /*
             * Passes a handle to this task and the
             * current state to the class that created
             * the thread pools
             */
            PhotoManager.handleState(this, state)
        }
        ...
    }
    

Java

    public class PhotoTask {
        ...
        // Gets a handle to the object that creates the thread pools
        photoManager = 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
             */
            photoManager.handleState(this, state);
        }
        ...
    }
    

将数据移动到界面

PhotoManager 对象从 PhotoTask 对象接收状态代码和 PhotoTask 对象的句柄。由于状态为 TASK_COMPLETE,因此创建一个包含状态和任务对象的 Message,并将其发送给 Handler

Kotlin

    object PhotoManager {
        ...
        // Handle status messages from tasks
        fun handleState(photoTask: PhotoTask, state: Int) {
            when(state) {
                ...
                TASK_COMPLETE -> { // The task finished downloading and decoding the image
                    /*
                     * Creates a message for the Handler
                     * with the state and the task object
                     */
                    handler.obtainMessage(state, photoTask)?.apply {
                        sendToTarget()
                    }
                }
                ...
            }
            ...
        }
    

Java

    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 =
                            handler.obtainMessage(state, photoTask);
                    completeMessage.sendToTarget();
                    break;
                ...
            }
            ...
        }
    

最后,Handler.handleMessage() 会检查传入的每一条 Message 的状态代码。如果状态代码为 TASK_COMPLETE,则任务已完成,并且 Message 中的 PhotoTask 对象会同时包含 BitmapImageView。由于 Handler.handleMessage() 在界面线程上运行,因此它可以安全地将 Bitmap 移到 ImageView 中:

Kotlin

        object PhotoManager {
            ...
            private val handler: Handler = object : Handler(Looper.getMainLooper()) {

                override fun handleMessage(inputMessage: Message) {
                    // Gets the image task from the incoming Message object.
                    val photoTask = inputMessage.obj as PhotoTask
                    // Gets the ImageView for this task
                    val localView: PhotoView = photoTask.getPhotoView()
                    ...
                    when (inputMessage.what) {
                        ...
                        TASK_COMPLETE -> localView.setImageBitmap(photoTask.image)
                        ...
                        else -> super.handleMessage(inputMessage)
                    }
                    ...
                }
                ...
            }
            ...
        ...
        }
    ...
    }
    

Java

        private PhotoManager() {
            ...
                handler = 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