English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Descripción detallada del mecanismo de mensajería de Android y ejemplos de código

Mecanismo de mensajería de Android

1.Resumen

Al inicio de la ejecución de una aplicación Android, se crea por defecto una línea principal (hilo UI), en esta línea se asocia una cola de mensajes (MessageQueue), todas las operaciones se encapsularán en la cola de mensajes y se entregarán a la línea principal para su procesamiento. Para evitar que la línea principal salga, se colocarán las operaciones de la cola de mensajes en un ciclo infinito, el programa parece ejecutar un ciclo infinito, y cada vez que se complete un ciclo, se extraerá un mensaje de la cola de mensajes interna, luego se llamará al función de manejo de mensajes (handlerMessage), después de completar el procesamiento de un mensaje, continuará el ciclo, si la cola de mensajes está vacía, el hilo se bloqueará y esperará. Por lo tanto, no se cerrará. Como se muestra en la siguiente imagen:

¿Cuál es la relación entre Handler, Looper y Message?

En muchos casos, para completar operaciones de larga duración en un subhilo, es necesario actualizar la interfaz de usuario, lo más común es publicar un mensaje a través de Handler en el hilo de la interfaz de usuario, y luego procesarlo en el método handlerMessage de Handler. Cada Handler se asocia con una cola de mensajes (MessageQueue), y Looper es responsable de crear una MessageQueue, y cada Looper se asocia con un hilo (Looper se encapsula mediante ThreadLocal). Por defecto, MessageQueue tiene solo uno, es decir, la cola de mensajes del hilo principal.

Aquí está el principio básico del mecanismo de mensajes de Android, si desea obtener más detalles, comencemos desde el código fuente.

2.Análisis del código fuente

(1)Iniciar el ciclo de mensajes Looper en el hilo principal ActivityThread

public final class ActivityThread {
  public static void main(String[] args) {
    //Código omitido
    //1.Crear el Looper del ciclo de mensajes
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
      sMainThreadHandler = thread.getHandler();
    }
    AsyncTask.init();
    //2.Ejecutar el ciclo de mensajes
    Looper.loop();
    throw new RuntimeException("La cola de mensajes del hilo principal salió inesperadamente");
  }
}

ActivityThread crea la cola de mensajes del hilo principal mediante Looper.prepareMainLooper() y finalmente ejecuta Looper.loop() para iniciar la cola de mensajes. Handler asocia la cola de mensajes y el hilo.

(2)Handler asocia la cola de mensajes y el hilo

public Handler(Callback callback, boolean async) {
    //Código omitido
    //Obtener Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
        "No se puede crear un handler dentro de un hilo que no ha llamado a Looper.prepare()";
    }
    //Obtener la cola de mensajes
    mQueue = mLooper.mQueue;
  }

Handler internamente obtiene el objeto Looper mediante el método Looper.getLooper() y se asocia con él, y obtiene la cola de mensajes. ¿Cómo funciona Looper.getLooper()?

  public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
  }
  public static @NonNull MessageQueue myQueue() {
    return myLooper().mQueue;
  }
  public static void prepare() {
    prepare(true);
  }
  //Establecer un Looper para el hilo actual
  private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
      throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
  }
  //Configurar el Looper del hilo UI
  public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
      if (sMainLooper != null) {
        throw new IllegalStateException("The main Looper has already been prepared.");
      }
      sMainLooper = myLooper();
    }
  }

En la clase Looper, el método myLooper(), se obtiene mediante sThreadLocal.get(), se llama al método prepare() en prepareMainLooper(), en este método se crea un objeto Looper y se configura sThreadLocal(). De esta manera, la cola se asocia con el hilo. A través del método sThreadLocal.get(), se garantiza que diferentes hilos no puedan acceder a las colas de mensajes del otro.

¿Por qué el Handler que actualiza UI debe crearse en el hilo principal?

Porque el Handler debe estar asociado con la cola de mensajes del hilo principal, de esta manera handlerMessage se ejecutará en el hilo UI, en este momento el hilo UI es seguro.

(3)Bucle de mensajes, procesamiento de mensajes

La creación del bucle de mensajes es a través del método Looper.loop(). El código fuente es el siguiente:

/**
   * Ejecute la cola de mensajes en este hilo. Asegúrese de llamar
   * Llame a {@link #quit()} para finalizar el bucle.
   */
  public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
      throw new RuntimeException("No Looper; Looper.prepare() no se llamó en este hilo.");
    }
    //1.Obtener cola de mensajes
    final MessageQueue queue = me.mQueue;
    //2.Bucle infinito, es decir, bucle de mensajes
    for (;;) {
      //3.Obtener mensaje, puede bloquearse
      Message msg = queue.next(); // puede bloquearse
      if (msg == null) {
        // No hay mensaje indica que la cola de mensajes está saliendo.
        return;
      }
      //4.Procesar mensaje
      msg.target.dispatchMessage(msg);
      //Reciclar mensaje
      msg.recycleUnchecked();
    }
  }

Desde el programa anterior podemos ver que la esencia del método loop() es crear un bucle infinito, luego extraer mensajes uno por uno de la cola de mensajes y finalmente procesarlos. Para Looper: se crea el objeto Looper (la cola de mensajes está encapsulada en el objeto Looper) mediante Looper.prepare(), y se almacena en sThreadLocal, luego se realiza el bucle de mensajes mediante Looper.loop(), estos dos pasos suelen aparecer juntos.

public final class Message implements Parcelable {
  //procesado por target
  Handler target; 
  //callback de tipo Runnable
  Runnable callback;
  //El siguiente mensaje, la cola de mensajes se almacena en forma de cadena.
  Message next;
}

Desde el código fuente se puede ver que target es del tipo Handler. En realidad es un ciclo, que envía mensajes a la cola de mensajes a través de Handler, y luego la cola de mensajes distribuye los mensajes para que los Handler los procesen. En la clase Handle:

//Función de manejo de mensajes, sobrescribe la subclase
public void handleMessage(Message msg) {
}
private static void handleCallback(Message message) {
    message.callback.run();
  }
//Distribuir mensaje
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
      handleCallback(msg);
    } else {
      if (mCallback != null) {
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      handleMessage(msg);
    }
  }

A partir del programa anterior, podemos ver que dispatchMessage es solo un método de distribución, si el callback de tipo Runnable está vacío, se ejecuta handleMessage para manejar el mensaje, este método está vacío, escribiremos el código de actualización de la UI en esta función; si el callback no está vacío, se ejecuta handleCallback para manejar, este método llama al método run del callback. De hecho, son dos tipos de distribución de Handler, por ejemplo, si post(Runnable callback) no está vacío, cuando usamos Handler para sendMessage generalmente no se configura callback, por lo tanto, se ejecuta handleMessage.

 public final boolean post(Runnable r)
  {
    return sendMessageDelayed(getPostMessage(r), 0);
  }
  public String getMessageName(Message message) {
    if (message.callback != null) {
      return message.callback.getClass().getName();
    }
    return "0x" + Integer.toHexString(message.what);
  }
  public final boolean sendMessageDelayed(Message msg, long delayMillis)
  {
    if (delayMillis < 0) {
      delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
      RuntimeException e = new RuntimeException(
          this + " sendMessageAtTime() llamado con sin mQueue");
      Log.w("Looper", e.getMessage(), e);
      return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
  }

Desde el programa mencionado anteriormente se puede ver que al llamar a post(Runnable r), se envuelve el Runnable en un objeto Message y se establece el objeto Runnable como el callback del objeto Message, y finalmente se inserta el objeto en la cola de mensajes. sendMessage también se implementa de manera similar:

public final boolean sendMessage(Message msg)
  {
    return sendMessageDelayed(msg, 0);
  }

Ya sea que se envíe un Runnable o un Message, se llamará al método sendMessageDelayed(msg, time). El Handler finalmente añade el mensaje a la MessageQueue, y el Looper lee continuamente mensajes de la MessageQueue, llamando al método dispatchMessage del Handler para distribuir los mensajes. De esta manera, los mensajes se generan, se añaden a la MessageQueue y se procesan por el Handler, lo que permite que la aplicación Android funcione.

3.Pruebas

new Thread(){
  Handler handler = null;
  public void run () {
    handler = new Handler();
  ;
.start();

¿Tiene algún problema el código mencionado anteriormente?

El objeto Looper es ThreadLocal, lo que significa que cada hilo utiliza su propio Looper, que puede estar vacío. Sin embargo, cuando se crea un objeto Handler en un hilo secundario y el Looper está vacío, se producirá una excepción.

public Handler(Callback callback, boolean async) {
    //Código omitido
    //Obtener Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
        "No se puede crear un handler dentro de un hilo que no ha llamado a Looper.prepare()";
    }
    //Obtener la cola de mensajes
    mQueue = mLooper.mQueue;
  }

Cuando mLooper esté vacío, lance una excepción. Esto se debe a que el objeto Looper no se ha creado, por lo que sThreadLocal.get() devolverá null. El principio básico de Handler es establecer una asociación con MessageQueue y entregar mensajes a MessageQueue, si no hay MessageQueue, no tiene sentido que Handler exista, y MessageQueue está sellado en Looper, por lo que al crear Handler, Looper no puede estar vacío. La solución es la siguiente:

new Thread(){
  Handler handler = null;
  public void run () {
    //Crear un Looper para la hilo actual y bindearlo a ThreadLocal
    Looper.prepare();
    handler = new Handler();
    //Iniciar el bucle de mensajes
    Looper.loop();
  ;
.start();

Si solo crea un Looper sin iniciar el bucle de mensajes, aunque no lanzará excepciones, ni post ni sendMessage() a través de handler también será ineficaz. Porque aunque los mensajes se agregarán a la cola de mensajes, pero no se iniciará el bucle de mensajes, por lo que no se obtendrán mensajes de la cola de mensajes y se ejecutarán.

Gracias por leer, espero que pueda ayudar a todos, gracias por el apoyo a nuestro sitio!

Te gustará