¿Cuál es la relación entre Looper, Handler y MessageQueue en Android?


95

He comprobado la documentación / guía oficial para Android Looper, Handlery MessageQueue. Pero no pude conseguirlo. Soy nuevo en Android y me confundí mucho con estos conceptos.

Respuestas:


103

A Looperes un bucle de manejo de mensajes: lee y procesa elementos de a MessageQueue. La Looperclase generalmente se usa junto con a HandlerThread(una subclase de Thread).

A Handleres una clase de utilidad que facilita la interacción con a, Looperprincipalmente mediante la publicación de mensajes y Runnableobjetos en el hilo MessageQueue. Cuando Handlerse crea un, está vinculado a un Looperhilo específico y a una cola de mensajes asociados.

En el uso típico, usted crea e inicia un HandlerThread, luego crea un Handlerobjeto (u objetos) mediante los cuales otros subprocesos pueden interactuar con la HandlerThreadinstancia. El Handlerdebe crearse mientras que se ejecuta en el HandlerThread, aunque una vez creado no hay ninguna restricción en lo que los hilos pueden utilizar los Handler's métodos de programación ( post(Runnable), etc.)

El subproceso principal (también conocido como subproceso de interfaz de usuario) en una aplicación de Android se configura como un subproceso de controlador antes de que se cree la instancia de la aplicación.

Aparte de los documentos de la clase, hay una buena discusión sobre todo esto aquí .

PD: Todas las clases mencionadas anteriormente están en el paquete android.os.


@Ted Hopp - ¿La cola de mensajes de Looper es diferente de la cola de mensajes de Thread?
CopsOnRoad

2
@Jack - Son lo mismo. Los documentos de la API deMessageQueue Android indican que a MessageQueuees una " clase de bajo nivel que contiene la lista de mensajes que debe enviar aLooper " .
Ted Hopp

95

Es ampliamente conocido que es ilegal actualizar los componentes de la interfaz de usuario directamente desde subprocesos distintos del subproceso principal en Android. Este documento de Android ( Manejo de operaciones costosas en el subproceso de la interfaz de usuario ) sugiere los pasos a seguir si necesitamos iniciar un subproceso por separado para hacer un trabajo costoso y actualizar la interfaz de usuario una vez hecho. La idea es crear un objeto Handler asociado con el hilo principal y publicar un Runnable en el momento adecuado. Esto Runnablese invocará en el hilo principal . Este mecanismo se implementa con las clases Looper y Handler .

La Looperclase mantiene un MessageQueue , que contiene una lista de mensajes . Un carácter importante de Looper es que está asociado con el hilo dentro del cual Looperse crea . Esta asociación se mantiene para siempre y no se puede romper ni cambiar. También tenga en cuenta que un hilo no se puede asociar con más de uno Looper. Para garantizar esta asociación, Looperse almacena en el almacenamiento local de subprocesos y no se puede crear directamente a través de su constructor. La única forma de crearlo es llamar a prepare método estático en Looper. preparar el método primero examina ThreadLocaldel hilo actual para asegurarse de que no haya un Looper asociado con el hilo. Después del examen, Looperse crea uno nuevo y se guarda en formato ThreadLocal. Una vez preparado el Looper, podemos llamar al método loop para comprobar si hay nuevos mensajes y tener Handlerque lidiar con ellos.

Como su nombre lo indica, la Handlerclase es principalmente responsable de manejar (agregar, eliminar, enviar) los mensajes de los hilos actuales MessageQueue. Una Handlerinstancia también está vinculada a un hilo. El enlace entre Handler y Thread se logra mediante Loopery MessageQueue. A siempreHandler está vinculado a a Looper, y posteriormente vinculado al subproceso asociado con Looper. A diferencia de lo que sucede Looper, varias instancias de Handler pueden vincularse al mismo hilo. Siempre que llamamos a la publicación o cualquier método similar en Handler, se agrega un nuevo mensaje al archivo asociado MessageQueue. El campo de destino del mensaje se establece en la Handlerinstancia actual . Cuando elLooperrecibió este mensaje, invoca dispatchMessage en el campo de destino del mensaje, de modo que el mensaje se enruta de regreso a la instancia de Handler para ser manejado, pero en el hilo correcto. Las relaciones entre Looper, Handlery MessageQueuese muestra a continuación:

ingrese la descripción de la imagen aquí


5
¡Gracias! pero, ¿cuál es el punto si el administrador primero publica el mensaje en la cola de mensajes y luego maneja el mensaje desde la misma cola? ¿Por qué no maneja el mensaje directamente?
Blake

4
@Blake b / c está publicando desde un hilo (hilo no looper) pero manejando el mensaje en otro hilo (hilo
looper

Mucho mejor de lo que se documenta en developer.android.com , pero sería bueno ver el código del diagrama que ha proporcionado.
tfmontague

@numansalati: ¿Handler no puede publicar mensajes desde el hilo del looper?
CopsOnRoad

78

Comencemos con el Looper. Puede comprender la relación entre Looper, Handler y MessageQueue más fácilmente cuando comprenda qué es Looper. También puede comprender mejor qué es Looper en el contexto del marco GUI. Looper está hecho para hacer 2 cosas.

1) Looper transforma un hilo normal , que termina cuando su run()método regresa, en algo que se ejecuta continuamente hasta que se ejecuta la aplicación de Android , lo cual es necesario en el marco de la GUI (técnicamente, aún termina cuando el run()método regresa. Pero déjame aclarar lo que quiero decir, abajo).

2) Looper proporciona una cola donde se colocan los trabajos a realizar, que también es necesaria en el marco de la GUI.

Como sabrá, cuando se inicia una aplicación, el sistema crea un hilo de ejecución para la aplicación, llamado "principal", y las aplicaciones de Android normalmente se ejecutan en su totalidad en un solo hilo por defecto, el "hilo principal". Pero el hilo principal no es un hilo especial secreto . Es solo un hilo normal que también puede crear con new Thread()código, lo que significa que termina cuando su run()método regresa. Piense en el siguiente ejemplo.

public class HelloRunnable implements Runnable {
    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }
}

Ahora, apliquemos este principio simple a la aplicación de Android. ¿Qué pasaría si una aplicación de Android se ejecuta en un hilo normal? Un hilo llamado "principal" o "UI" o lo que sea inicia la aplicación y dibuja toda la UI. Entonces, la primera pantalla se muestra a los usuarios. ¿Y ahora qué? ¿El hilo principal termina? No, no debería. Debería esperar hasta que los usuarios hagan algo, ¿verdad? Pero, ¿cómo podemos lograr este comportamiento? Bueno, podemos intentarlo con Object.wait()oThread.sleep(). Por ejemplo, el subproceso principal finaliza su trabajo inicial para mostrar la primera pantalla y se pone en suspensión. Se despierta, lo que significa interrumpido, cuando se busca un nuevo trabajo. Hasta ahora todo va bien, pero en este momento necesitamos una estructura de datos similar a una cola para contener varios trabajos. Piense en un caso en el que un usuario toca la pantalla en serie y una tarea tarda más en finalizar. Por lo tanto, necesitamos tener una estructura de datos para mantener los trabajos que se deben realizar de la manera primero en entrar, primero en salir. Además, puede imaginarse que implementar un subproceso que siempre se ejecuta y procesa el trabajo cuando llega utilizando la interrupción no es fácil y conduce a un código complejo y, a menudo, no mantenible. Preferimos crear un nuevo mecanismo para tal propósito, y de eso se trata Looper . El documento oficial de la clase Looperdice, "Los subprocesos por defecto no tienen un bucle de mensaje asociado", y Looper es una clase "usada para ejecutar un bucle de mensaje para un hilo". Ahora puedes entender lo que significa.

Pasemos a Handler y MessageQueue. Primero, MessageQueue es la cola que mencioné anteriormente. Reside dentro de un Looper, y eso es todo. Puedes comprobarlo con el código fuente de la clase Looper . La clase Looper tiene una variable miembro de MessageQueue.

Entonces, ¿qué es Handler? Si hay una cola, entonces debería haber un método que nos permita poner una nueva tarea en la cola, ¿verdad? Eso es lo que hace Handler. Podemos poner en cola una nueva tarea en una cola (MessageQueue) usando varios post(Runnable r)métodos. Eso es. Se trata de Looper, Handler y MessageQueue.

Mi última palabra es que, básicamente, Looper es una clase que está hecha para abordar un problema que ocurre en el marco de la GUI. Pero este tipo de necesidades también pueden ocurrir en otras situaciones. En realidad, es un patrón bastante famoso para aplicaciones de subprocesos múltiples, y puedes aprender más sobre él en "Programación concurrente en Java" de Doug Lea (especialmente, el capítulo 4.1.4 "Subprocesos de trabajo" sería útil). Además, puede imaginar que este tipo de mecanismo no es único en el marco de Android, pero todos los marcos de GUI pueden necesitar algo similar a esto. Puede encontrar casi el mismo mecanismo en el marco de Java Swing.


4
La mejor respuesta. Aprendí más de esta explicación detallada. Me pregunto si hay alguna publicación de blog que sea más detallada.
capt

¿Se pueden agregar los mensajes a MessageQueue sin usar Handler?
CopsOnRoad

@CopsOnRoad no, no se pueden agregar directamente.
Faisal Naseer

Hizo mi día ... mucho amor para ti :)
Rahul Matte

26

MessageQueue: Es una clase de bajo nivel que contiene la lista de mensajes que debe enviar un Looper. Los mensajes no se agregan directamente a a MessageQueue, sino a través de Handlerobjetos asociados con Looper. [ 3 ]

Looper: Se repite en un bucle MessageQueueque contiene los mensajes que se enviarán. La tarea real de administrar la cola la realiza el Handlerresponsable de manejar (agregar, eliminar, enviar) mensajes en la cola de mensajes. [ 2 ]

Handler: Se le permite enviar y procesar Messagey Runnableobjetos asociados con un hilo de MessageQueue. Cada instancia de Handler está asociada con un solo hilo y la cola de mensajes de ese hilo. [ 4 ]

Cuando crea un nuevo Handler, está vinculado al hilo / cola de mensajes del hilo que lo está creando; a partir de ese momento, entregará mensajes y ejecutables a esa cola de mensajes y los ejecutará a medida que salen de la cola de mensajes .

Por favor, revise la imagen de abajo [ 2 ] para una mejor comprensión.

ingrese la descripción de la imagen aquí


0

Ampliando la respuesta, por @K_Anas, con un ejemplo, como se indicó

Es ampliamente conocido que es ilegal actualizar los componentes de la interfaz de usuario directamente desde subprocesos que no sean el subproceso principal en Android.

por ejemplo, si intenta actualizar la interfaz de usuario mediante Thread.

    int count = 0;
    new Thread(new Runnable(){
        @Override
        public void run() {
            try {
                while(true) {
                    sleep(1000);
                    count++;
                    textView.setText(String.valueOf(count));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


   ).start();

su aplicación se bloqueará con excepción.

android.view.ViewRoot $ CalledFromWrongThreadException: solo el hilo original que creó una jerarquía de vistas puede tocar sus vistas.

en otras palabras, debe usar Handlerque mantiene la referencia a la tarea MainLooper ie Main Threado UI Thready pasar como Runnable.

  Handler handler = new Handler(getApplicationContext().getMainLooper);
        int count = 0;
        new Thread(new Runnable(){
            @Override
            public void run() {
                try {
                    while(true) {
                        sleep(1000);
                        count++;
                        handler.post(new Runnable() {
                           @Override
                           public void run() {
                                 textView.setText(String.valueOf(count));
                           }
                         });

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    ).start() ;
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.