Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

UI スレッドとの通信

スレッドプールのスレッドでコードを実行する方法を説明した前回のガイドでは、ThreadPoolExecutor によって管理されるスレッドでタスクを開始する方法を説明しました。この最後のレッスンでは、タスクからユーザー インターフェース(UI)スレッドで実行されているオブジェクトにデータを送信する方法を説明します。この機能を使用すると、タスクはバックグラウンド処理を行い、結果をビットマップなどの UI 要素に移動できます。

すべてのアプリには、View オブジェクトなどの UI オブジェクトを実行する独自の特別なスレッドがあり、このスレッドは UI スレッドと呼ばれます。UI スレッドで実行されているオブジェクトだけが、そのスレッド内の他のオブジェクトにアクセスできます。スレッドプールのスレッドで実行されるタスクは、UI スレッドで実行されていないため、UI オブジェクトにはアクセスできません。バックグラウンド スレッドから UI スレッドにデータを移動するには、UI スレッドで実行されている Handler を使用します。

UI スレッドでハンドラを定義する

Handler は、スレッドを管理するための Android システムのフレームワークの一部です。Handler オブジェクトはメッセージを受信し、メッセージを処理するコードを実行します。通常、Handler は新しいスレッドのために作成しますが、既存のスレッドに接続された Handler も作成できます。Handler を UI スレッドに接続すると、メッセージを処理するコードが UI スレッドで実行されます。

スレッドプールを作成するクラスの構築中に Handler オブジェクトをインスタンス化し、そのオブジェクトをグローバル変数に保存します。オブジェクトを Handler(Looper) コンストラクタでインスタンス化して、UI スレッドに接続します。このコンストラクタは、Android システムのスレッド管理フレームワークの別の部分である Looper オブジェクトを使用します。特定の 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 にデータを移動するよう指示する方法を説明します。

タスクから UI スレッドにデータを移動する

バックグラウンド スレッドで実行されているタスク オブジェクトから UI スレッド上のオブジェクトにデータを移動するには、まずデータと UI オブジェクトへの参照をタスク オブジェクトに保存します。次に、タスク オブジェクトとステータス コードを、Handler をインスタンス化したオブジェクトに渡します。このオブジェクトで、ステータスとタスク オブジェクトを含む MessageHandler に送信します。Handler は UI スレッドで実行されているため、データを UI オブジェクトに移動できます。

タスク オブジェクトにデータを保存する

たとえば、次の 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 には、Bitmap を表示する ImageView のハンドルも含まれています。BitmapImageView への参照は同じオブジェクト内にありますが、現在 UI スレッドで実行されていないため、BitmapImageView に割り当てることはできません。

代わりに、次の手順としてこのステータスを PhotoTask オブジェクトに送信します。

上位のオブジェクト階層にステータスを送信する

PhotoTask は、階層内で 1 つ上にあるオブジェクトです。デコードされたデータと、データを表示する 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);
        }
        ...
    }
    

データを UI に移動する

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 の場合、タスクは終了し、MessagePhotoTask オブジェクトには BitmapImageView の両方が含まれます。Handler.handleMessage() は UI スレッドで実行されているため、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 をダウンロードします。