Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.

Cómo comunicarse con el subproceso de IU

En la guía anterior, donde se explica cómo ejecutar código en un subproceso del conjunto de subprocesos, se muestra cómo iniciar una tarea en un subproceso que administra ThreadPoolExecutor. En esta lección final, se explica cómo enviar datos desde la tarea a objetos que se ejecutan en el subproceso de interfaz de usuario (IU). Esta función permite que las tareas se ejecuten en segundo plano y, luego, se muevan los resultados a elementos de IU como mapas de bits.

Cada app tiene su propio subproceso especial que ejecuta objetos de IU (como objetos View), conocido como subproceso de IU. Solo los objetos que se ejecutan en el subproceso de IU tienen acceso a otros objetos de ese subproceso. Como las tareas que realizas en un subproceso de un conjunto de subprocesos no se ejecutan en tu subproceso de IU, no pueden acceder a los objetos de IU. Para mover datos de un subproceso en segundo plano al subproceso de IU, usa el objeto Handler que se ejecuta en este último.

Cómo definir un controlador en el subproceso de IU

Handler forma parte del marco de trabajo del sistema Android para la administración de subprocesos. Un objeto Handler recibe mensajes y ejecuta código a fin de administrarlos. Por lo general, creas un Handler para un nuevo subproceso, pero también puedes crear un Handler que esté conectado a un subproceso existente. Cuando conectas un Handler a tu subproceso de IU, el código que administra los mensajes se ejecuta en ese subproceso.

Crea instancias del objeto Handler durante la construcción de la clase que crea tus conjuntos de subprocesos; luego, almacena el objeto en una variable global. Crea instancias con el constructor Handler(Looper) para conectar el objeto al subproceso de IU. Este constructor usa un objeto Looper, que es otra parte del marco de trabajo de administración de subprocesos del sistema Android. Cuando creas una instancia de Handler basada en una instancia específica de Looper, se ejecuta Handler en el mismo subproceso que Looper. Por ejemplo:

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

Dentro de Handler, anula el método handleMessage(). El sistema Android invoca este método cuando recibe mensajes nuevos para un subproceso que administra; todos los objetos Handler de un subproceso específico reciben el mismo mensaje. Por ejemplo:

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

En la siguiente sección, se muestra cómo indicar a Handler que mueva datos.

Cómo mover datos desde una tarea al subproceso de IU

Para mover datos de un objeto de tarea que se ejecuta en un subproceso en segundo plano a un objeto en el subproceso de IU, comienza por almacenar referencias a los datos y el objeto de IU en el objeto de tarea. A continuación, pasa el objeto de tarea y un código de estado al objeto en el que se crearon instancias de Handler. En este objeto, envía un Message que contenga el estado y el objeto de tarea a Handler. Como Handler se ejecuta en el subproceso de IU, puede mover los datos al objeto de IU.

Cómo almacenar datos en el objeto de tarea

Por ejemplo, aquí se muestra un objeto Runnable, que se ejecuta en un subproceso en segundo plano, que decodifica un Bitmap y lo almacena en su objeto principal PhotoTask. El objeto Runnable también almacena el código de estado 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 también contiene un controlador para ImageView que muestra el objeto Bitmap. Si bien las referencias a Bitmap y ImageView están en el mismo objeto, no puedes asignar Bitmap a ImageView porque actualmente no se está ejecutando en el subproceso de IU.

En su lugar, el próximo paso es enviar el estado al objeto PhotoTask.

Cómo enviar el estado para que escale en la jerarquía de objetos

PhotoTask es el siguiente objeto más arriba en la jerarquía. Contiene referencias a los datos decodificados y al objeto View que te mostrará los datos. Recibe el código de estado de PhotoDecodeRunnable y lo pasa al objeto que contiene los conjuntos de subprocesos y crea instancias de 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);
        }
        ...
    }
    

Cómo mover datos a la IU

Desde el objeto PhotoTask, el objeto PhotoManager recibe un código de estado y un controlador para PhotoTask. Como el estado es TASK_COMPLETE, este crea un Message que contiene el estado y el objeto de tarea. Luego, lo envía a 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;
                ...
            }
            ...
        }
    

Por último, Handler.handleMessage() comprueba el código de estado de cada Message entrante. Si es TASK_COMPLETE, entonces la tarea ha finalizado y el objeto PhotoTask en el Message incluye tanto un Bitmap como un ImageView. Como Handler.handleMessage() se ejecuta en el subproceso de IU, puede mover el Bitmap a ImageView de manera segura.

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

Más información

Para obtener más información sobre las operaciones de varios subprocesos en Android, consulta la siguiente guía: Descripción general de los procesos y subprocesos.

App de ejemplo

Para probar los conceptos de esta guía, descarga ThreadSample.