Handlers, MessageQueue, Looper, ¿se ejecutan todos en el hilo de UI?

Estoy tratando de entender el tema y sé que puedo usar un Handler para publicar mensajes / ejecutables en MessageQueue , que a su vez es recogido por el Looper y devuelto al manejador para su procesamiento.

Si MessageQueue en un controlador en mi actividad, ¿la Activity , MessageQueue , MessageQueue y Looper ejecutan en el hilo de UI? Si no, ¿podría alguien explicar cómo se une todo esto? 🙂

Respuesta corta: todos ejecutan en el mismo hilo. Si se crea una instancia a partir de una callback del ciclo de vida de la Activity , todos se ejecutan en el hilo de la interfaz de usuario principal.

Respuesta larga:

Un hilo puede tener un Looper , que contiene un MessageQueue . Para usar esta instalación, debería crear un Looper en el hilo actual llamando (lo estático) Looper.prepare() , y luego iniciar el ciclo llamando (también estático) Looper.loop() . Estos son estáticos porque solo se supone que hay un Looper por hilo.

La llamada a loop() generalmente no retorna durante un tiempo, pero sigue sacando mensajes (“tareas”, “comandos” o lo que quiera llamar) del MessageQueue y los maneja individualmente (por ejemplo, devolviendo una Runnable contenida) en el mensaje). Cuando no quedan mensajes en la cola, el hilo bloquea hasta que haya nuevos mensajes. Para detener un Looper , debe llamar a quit() en él (lo que probablemente no detiene el ciclo inmediatamente, sino que establece un indicador privado que se marca periódicamente desde el ciclo, indicándole que se detenga).

Sin embargo, no puede agregar mensajes a la cola directamente. En su lugar, registra un MessageQueue.IdleHandler para esperar una callback queueIdle() , en la que puede decidir si desea hacer algo o no. Todos los controladores se llaman a su vez. (Por lo tanto, la “cola” no es realmente una cola, sino una colección de devoluciones de llamadas para ser llamadas regularmente ).

Nota con respecto al párrafo anterior: Esto realmente lo adiviné. No pude encontrar ninguna documentación sobre eso, pero tendría sentido.

actualización: vea el comentario de ahcox y su respuesta .

Debido a que esto es mucho trabajo, el marco proporciona la clase Handler para simplificar las cosas . Cuando crea una instancia de Handler , está (por defecto) vinculada al Looper ya conectado al hilo actual. (El Handler sabe a qué Looper debe conectar porque llamamos a prepare() anteriormente, lo que probablemente almacenaba una referencia al Looper en un ThreadLocal ).

Con un Handler , puede simplemente llamar a post() para “poner un mensaje en la cola de mensajes del hilo” (por así decirlo). El Handler se encargará de todas las cosas de callback de IdleHandler y se asegurará de que se ejecute Runnable publicado. (También podría verificar si es el momento adecuado, si ha publicado con retraso).

Para que quede claro: la única forma de hacer que un hilo de bucle haga algo es publicar un mensaje en su ciclo. Esto es válido hasta que llame a quit () en el looper.


En cuanto al hilo de la interfaz de usuario de Android: en algún momento (probablemente antes de que se creen actividades similares) el marco ha configurado un Looper (que contiene un MessageQueue ) y lo ha iniciado. A partir de este punto, todo lo que sucede en el hilo de la interfaz de usuario pasa por ese ciclo. Esto incluye la gestión del ciclo de vida de la actividad, etc. Todas las devoluciones de llamada que anula ( onCreate() , onDestroy() …) se envían al menos indirectamente desde ese bucle. Puedes ver eso, por ejemplo, en el rastro de la stack de una excepción. (Puede intentarlo, simplemente escriba int a = 1 / 0; onCreate() int a = 1 / 0; en algún lugar de onCreate() …)


Espero que esto tenga sentido. Perdón por no haber sido claro previamente.

Siguiendo con la parte de “cómo todo se une”. Como escribió el usuario634618, el looper toma el hilo, el hilo principal de la IU en el caso del Looper principal de una aplicación.

  • Looper.loop() saca mensajes de su cola de mensajes. Cada Mensaje tiene una referencia a un Manejador asociado que debe devolverse a (el miembro de destino).
  • Dentro de Looper.loop() por cada mensaje obtenido de la cola:
    • loop() llama a public void Handler.dispatchMessage(Message msg) utilizando el Handler que se almacena en el mensaje como su miembro de destino.
    • Si el mensaje tiene un miembro de callback Runnable, se ejecuta.
    • De lo contrario, si el controlador tiene un conjunto de callback compartido, se ejecuta.
    • De lo contrario, handleMessage() Handler se llama con el mensaje como argumento. (Tenga en cuenta que si subclase Handler como AsyncTask lo hace, puede anular handleMessage() como lo hace).

En su pregunta acerca de que todos los objetos colaboradores están en el mismo hilo de interfaz de usuario, se debe crear un Handler en el mismo hilo que el Looper que enviará mensajes. Su constructor buscará el Looper actual y lo almacenará como miembro, vinculando el Handler a ese Looper . También hará referencia a la cola de mensajes de Looper directamente en su propio miembro. El Handler se puede utilizar para enviar trabajo al Looper desde cualquier hilo, pero esta identidad de las colas de mensajes enruta el trabajo para que se realice en el hilo de Looper .

Cuando estamos ejecutando algún código en otro hilo y queremos enviar un Runnable para ser ejecutado en el hilo de UI, podemos hacerlo así:

 // h is a Handler that we constructed on the UI thread. public void run_on_ui_thread(final Handler h, final Runnable r) { // Associate a Message with our Handler and set the Message's // callback member to our Runnable: final Message message = Message.obtain(h, r); // The target is the Handler, so this asks our Handler to put // the Message in its message queue, which is the exact same // message queue associated with the Looper on the thread on // which the Handler was created: message.sendToTarget(); } 

Intento implementar estas interfaces yo solo para entender el concepto. Por simplicidad, solo usa la interfaz por necesidad. Aquí está mi código de prueba:

 import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class TestLooper { public static void main(String[] args) { UIThread thread = new UIThread(); thread.start(); Handler mHandler = new Handler(thread.looper); new WorkThread(mHandler, "out thread").run(); } } class Looper { private BlockingQueue message_list = new LinkedBlockingQueue(); public void loop() { try { while (!Thread.interrupted()) { Message m = message_list.take(); m.exeute(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void insertMessage(Message msg) { message_list.add(msg); } } class Message { String data; Handler handler; public Message(Handler handler) { this.handler = handler; } public void setData(String data) { this.data = data; } public void exeute() { handler.handleMessage(this); } } class Handler { Looper looper; public Handler(Looper looper) { this.looper = looper; } public void dispatchMessage(Message msg) { System.out.println("Handler dispatchMessage" + Thread.currentThread()); looper.insertMessage(msg); } public Message obtainMessage() { return new Message(this); } public void handleMessage(Message m) { System.out.println("handleMessage:" + m.data + Thread.currentThread()); } } class WorkThread extends Thread { Handler handler; String tag; public WorkThread(Handler handler, String tag) { this.handler = handler; this.tag = tag; } public void run() { System.out.println("WorkThread run" + Thread.currentThread()); Message m = handler.obtainMessage(); m.setData("message " + tag); handler.dispatchMessage(m); } } class UIThread extends Thread { public Looper looper = new Looper(); public void run() { //create handler in ui thread Handler mHandler = new Handler(looper); new WorkThread(mHandler, "inter thread").run(); System.out.println("thead run" + Thread.currentThread()); looper.loop(); } } 

Si publico en un controlador en mi actividad, ¿la actividad, controlador, MessageQueue y Looper se ejecutan en el hilo de UI? Si no, ¿podría alguien explicar cómo se une todo esto? 🙂

Depende de cómo crees Handler

Caso 1:

 Handler() 

El constructor predeterminado asocia este controlador con el Looper para el hilo actual.

Si crea Handler esta manera en el hilo de UI, Handler se asocia con Looper de UI Thread. MessageQueue también está asociado con Looper con UI Thread.

Caso 2

 Handler (Looper looper) 

Use el Looper proporcionado en lugar del predeterminado.

Si creo un HandlerThread y paso el Looper de HandlerThread a Handler, Handler y Looper están asociados con HandlerThread y no con UI Thread. Handler , MessageQueue y Looper están asociados con HandlerThread .

Caso de uso: Desea ejecutar una operación de red o IO. No puede ejecutarlo en UI Thread y, por HandlerThread tanto, HandlerThread es útil para usted.

  HandlerThread handlerThread = new HandlerThread("NetworkOperation"); handlerThread.start(); Handler requestHandler = new Handler(handlerThread.getLooper()); 

Si desea pasar datos de HandlerThread a UI Thread, puede crear otro Handler (por ejemplo, responseHandler) con Looper desde UI Thread y llamar sendMessage . El handleMessage responseHandler subproceso UI debe prevalecer sobre handleMessage

Consulte estas publicaciones para obtener más detalles.

¿Cuál es el propósito de Looper y cómo usarlo? (Para conceptos)

Android: brindis en un hilo (por ejemplo, código al vincular todos estos conceptos)