Google berkomitmen untuk mendorong terwujudnya keadilan ras bagi komunitas Kulit Hitam. Lihat caranya.

Berkomunikasi dengan UI thread

Panduan sebelumnya yang memberi tahu cara Menjalankan kode pada rangkaian kumpulan thread, menunjukkan cara memulai tugas pada thread yang dikelola oleh ThreadPoolExecutor. Tutorial terakhir ini menunjukkan cara mengirim data dari tugas ke objek yang berjalan pada thread antarmuka pengguna (UI). Fitur ini memungkinkan tugas Anda melakukan pekerjaan latar belakang lalu memindahkan hasilnya ke elemen UI seperti bitmap.

Setiap aplikasi memiliki thread khususnya sendiri yang menjalankan objek UI seperti objek View; thread ini disebut UI thread. Hanya objek yang berjalan pada UI thread yang memiliki akses ke objek lain di thread tersebut. Karena tugas yang Anda jalankan di thread dari kumpulan thread tidak berjalan pada UI thread Anda, thread tersebut tidak memiliki akses ke objek UI. Untuk memindahkan data dari thread latar belakang ke UI thread, gunakan Handler yang berjalan pada UI thread.

Menentukan pengendali pada UI thread

Handler adalah bagian dari framework sistem Android untuk mengelola thread. Objek Handler menerima pesan dan menjalankan kode untuk menangani pesan. Biasanya, Anda membuat Handler untuk thread baru, tetapi Anda juga dapat membuat Handler yang terhubung ke thread yang ada. Saat menghubungkan Handler ke UI thread Anda, kode yang menangani pesan berjalan pada UI thread.

Buat instance objek Handler selama pembuatan class yang membuat kumpulan thread, dan simpan objek tersebut dalam variabel global. Hubungkan objek tersebut ke UI thread dengan membuat instance dengan konstruktor Handler(Looper). Konstruktor ini menggunakan objek Looper, yang merupakan bagian lain dari framework pengelolaan thread sistem Android. Saat Anda membuat instance Handler berdasarkan instance Looper tertentu, Handler akan berjalan pada thread yang sama dengan Looper. Contoh:

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()) {
        ...
    

Di dalam Handler, ganti metode handleMessage(). Sistem Android akan memanggil metode ini saat menerima pesan baru untuk thread yang dikelolanya; semua objek Handler untuk thread tertentu menerima pesan yang sama. Contoh:

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;
                ...
            }
        ...
        }
    }
    

Bagian berikutnya menunjukkan cara memberi tahu Handler untuk memindahkan data.

Memindahkan data dari tugas ke UI thread

Untuk memindahkan data dari objek tugas yang berjalan di thread latar belakang ke objek UI thread, mulailah dengan menyimpan referensi ke data dan ke objek UI pada objek tugas. Selanjutnya, teruskan objek tugas dan kode status ke objek yang membuat instance Handler. Dalam objek ini, kirim Message yang berisi status dan objek tugas ke Handler. Karena Handler berjalan pada UI thread, ia dapat memindahkan data ke objek UI.

Menyimpan data dalam objek tugas

Misalnya, berikut adalah Runnable, yang berjalan di thread latar belakang, yang mendekode Bitmap dan menyimpannya di PhotoTask objek induknya. Runnable juga menyimpan kode status 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 juga berisi tuas untuk ImageView yang menampilkan Bitmap. Meskipun referensi ke Bitmap dan ImageView berada di objek yang sama, Anda tidak dapat menetapkan Bitmap ke ImageView, karena saat ini Anda tidak berjalan di UI thread.

Sebagai gantinya, langkah selanjutnya adalah mengirim status ini ke objek PhotoTask.

Mengirim status ke hierarki objek

PhotoTask adalah objek tertinggi berikutnya dalam hierarki. Objek tersebut mempertahankan referensi ke data yang telah didekode dan objek View yang akan menampilkan datanya. Objek ini menerima kode status dari PhotoDecodeRunnable lalu meneruskannya ke objek yang mengelola kumpulan thread dan membuat instance 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);
        }
        ...
    }
    

Memindahkan data ke UI

Dari objek PhotoTask, objek PhotoManager menerima kode status dan tuas ke objek PhotoTask. Karena statusnya adalah TASK_COMPLETE, buat Message yang berisi status dan objek tugas, lalu mengirimkannya ke 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;
                ...
            }
            ...
        }
    

Terakhir, Handler.handleMessage() memeriksa kode status untuk setiap Message yang masuk. Jika kode status adalah TASK_COMPLETE, tugas telah selesai, dan objek PhotoTask di Message berisi Bitmap dan ImageView. Karena Handler.handleMessage() berjalan di UI thread, Bitmap dapat dipindahkan ke ImageView dengan aman:

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);
                        }
                        ...
                    }
                    ...
                }
                ...
        }
    ...
    }
    

Informasi selengkapnya

Untuk mempelajari operasi multi thread di Android lebih lanjut, lihat panduan Ringkasan proses dan thread.

Contoh aplikasi

Untuk mencoba konsep dalam panduan ini, download ThreadSample.