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

Mecanismo de mensajería de Android y el derrame de memoria del handler

Handler

Cada principiante en el desarrollo de Android no puede evitar el “paso” de Handler, ¿por qué decimos que es un paso? Primero, es uno de los puntos culminantes de la arquitectura de Android, y en segundo lugar, la mayoría de las personas saben lo que es, pero no cómo es. Después de ver el método Handler.post, decidí revisar el código fuente y entender el mecanismo de implementación de Handler.

Actualización asincrónica de la interfaz de usuario

Comencemos con un lema obligatorio: “La hilera principal no debe realizar operaciones de larga duración, y la hilera secundaria no debe actualizar la interfaz de usuario”. Este principio debería ser conocido por los principiantes, ¿cómo resolveremos el problema del lema? En este momento, Handler aparece frente a nosotros (AsyncTask también es posible, pero esencialmente es un encapsulamiento de Handler), aquí hay un código clásico y común (ignoramos el problema de la fuga de memoria, que trataremos más adelante):

Primero, en Activity, crea un nuevo handler:

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      switch (msg.what) {
        case 0:
          mTestTV.setText("This is handleMessage");//更新UI
          break;
      }
    }
  ;

然后在子线程里发送消息:

new Thread(new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(1000);//在子线程有一段耗时操作,比如请求网络
          mHandler.sendEmptyMessage(0);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }).start();

至此完成了在子线程的耗时操作完成后在主线程异步更新UI,可是并没有用上标题的post,我们再来看post的版本:

new Thread(new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(1000);//在子线程有一段耗时操作,比如请求网络
          Handler handler = new Handler();
          handler.post(new Runnable() {
            @Override
            public void run() {
              mTestTV.setText("This is post");//更新UI
            }
          });
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }).start();

从表面上来看,给post方法传了个Runnable,像是开了个子线程,可是在子线程里并不能更新UI啊,那么问题来了,这是怎么个情况呢?带着这个疑惑,来翻翻Handler的源码:

先来看看普通的sendEmptyMessage是什么样子:

public final boolean sendEmptyMessage(int what)
  {
    return sendEmptyMessageDelayed(what, 0);
  }
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
  }

然后将我们传入的参数封装成了一个消息,然后调用sendMessageDelayed:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
  {
    if (delayMillis < 0) {}}
      delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
  }

Llamamos a sendMessageAtTime de nuevo:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
      RuntimeException e = new RuntimeException;
          this + " sendMessageAtTime() called with no mQueue");
      Log.w("Looper", e.getMessage(), e);
      return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
  }

Bien, volvamos a ver post():

public final boolean post(Runnable r)
  {
    return sendMessageDelayed(getPostMessage(r), 0);//El método getPostMessage es la diferencia entre dos formas de enviar mensajes
  }

El método solo tiene una línea, la implementación interna es igual que sendMessage normal, pero hay un punto diferente, es decir, el método getPostMessage(r):

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
  }

Este método también encapsula los parámetros que nos pasamos en un mensaje, solo que esta vez es m.callback = r, antes era msg.what=what, y no nos fijamos en las propiedades de Message

Mecanismo de mensajes de Android

Aquí, solo sabemos que los principios de post y sendMessage son encapsulados en Message, pero aún no estamos claros sobre la mecánica completa de Handler, sigamos investigando.

Justo ahora vi que esos dos métodos al final todos llamaron a sendMessageAtTime

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
      RuntimeException e = new RuntimeException;
          this + " sendMessageAtTime() called with no mQueue");
      Log.w("Looper", e.getMessage(), e);
      return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
  }

Este método llama a enqueueMessage nuevamente, por lo que el nombre sugiere que es para agregar mensajes a la cola, veamos adentro:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
      msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
  }

Dejemos de lado mAsynchronous, relacionado con el asincronismo, y continuemos pasando los parámetros al método enqueueMessage de queue, y luego veremos la asignación de msg.target más adelante, ahora sigamos al método enqueueMessage de la clase MessageQueue, el método es bastante largo, pero veamos las líneas clave:

Message prev;
for (;;) {
  prev = p;
  p = p.next;
  if (p == null || when < p.when) {
    break;
  }
  if (needWake && p.isAsynchronous()) {
    needWake = false;
  }
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;

De hecho, como sugiere el nombre del método, un bucle infinito agrega mensajes a la cola de mensajes (en forma de lista enlazada), pero si se coloca, también debe retirarse, ¿cómo se puede extraer este mensaje?

Mirando en el método MessageQueue, encontramos next(), el código es demasiado largo para detallar, pero sabemos que se utiliza para extraer los mensajes. Sin embargo, ¿dónde se llama a este método? No es en el Handler, encontramos al personaje clave Looper, lo llamo mensajero circular, responsable de obtener mensajes de la cola de mensajes, el código clave es el siguiente:

for (;;) {
   Message msg = queue.next(); // puede bloquear
   ...
   msg.target.dispatchMessage(msg);
   ...
   msg.recycleUnchecked();
}

Claro y simple, vimos lo que dijimos msg.target recientemente, recientemente se asignó msg.target = this en el Handler, por lo tanto, veamos dispatchMessage en el Handler:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
      handleCallback(msg);
    } else {
      if (mCallback != null) {
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      handleMessage(msg);
    }
  }

1.Si el callback de msg no está vacío, llamar al método handleCallback (message.callback.run())
2.Si mCallback no está vacío, llamar a mCallback.handleMessage(msg)
3.Finalmente, si todo lo demás está vacío, ejecutar el método handleMessage(msg) de Handler
Debería haberse imaginado lo que es el callback de msg, es el método run del Runnable que pasamos a través de Handler.post(Runnable r), aquí hay que mencionar la base de java, llamar directamente al método run de la hilera es equivalente a llamar a un método en una clase común, se ejecuta en la línea actual, sin abrir una nueva hilera.

Por lo tanto, aquí hemos resuelto la confusión inicial, por qué incluso al pasar un Runnable en post, se puede actualizar UI en la línea principal.

Continuemos a ver si msg.callback está vacío en el caso de mCallback, esto requiere revisar el método constructor:

1.
public Handler() {
    this(null, false);
  }
2.  
public Handler(Callback callback) {
    this(callback, false);
  }
3.
public Handler(Looper looper) {
    this(looper, null, false);
  }
4.
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
  }
5.
public Handler(boolean async) {
    this(null, async);
  }
6.
public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {}}
      final Class<? extends Handler> klass = getClass();
      if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
          (klass.getModifiers() & Modifier.STATIC) == 0) {
        Log.w(TAG, "La siguiente clase Handler debe ser estática o podrían ocurrir fugas: " +
          klass.getCanonicalName());
      }
    }
    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()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
  }
7.
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
  }

La implementación específica solo incluye los dos últimos, ya que sabemos cómo se obtiene mCallback, se puede pasar en el constructor.

Finalmente, si ambos callbacks están vacíos, se ejecuta el método handleMessage(msg) del Handler propio, es decir, el handleMessage que se sobrescribe al crear el Handler.

Looper

Vimos aquí una duda, es que al crear un Handler no hemos introducido ningún parámetro, ni hay ningún lugar que muestre que se ha llamado a métodos relacionados con Looper, ¿dónde está la creación de Looper y la llamada a métodos? En realidad, todo esto lo ha hecho Android por nosotros, podemos encontrarlo en el método main del punto de entrada del programa ActivityThread:

 public static void main(String[] args) {
  ...
  Looper.prepareMainLooper();
  ...
  Looper.loop();
  ...

Resumen

He hecho un resumen general del mecanismo de mensajes de Handler, así como de la diferencia entre los métodos post y sendMessage que usamos comúnmente. Resumamos, principalmente involucra cuatro clases: Handler, Message, MessageQueue y Looper:

Se crea un nuevo Handler, se envían mensajes mediante sendMessage o post, y el Handler llama a sendMessageAtTime para entregar Message a MessageQueue

El método enqueueMessage de MessageQueue coloca Message en la cola en forma de lista

El método loop de Looper llama repetidamente a MessageQueue.next() para extraer mensajes y luego llama al dispatchMessage de Handler para procesar mensajes

En dispatchMessage, se�断定msg.callback、mCallback, es decir, el método post o el constructor que se transfiere no está vacío, y se ejecutan sus devoluciones de llamada. Si ambos están vacíos, se ejecuta el handleMessage que usamos más a menudo.

Hablemos del problema de fugas de memoria del handler
Vamos a ver el código de nuestro nuevo Handler:

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      ...
    }
  ;

Cuando se utiliza una clase interna (incluidas las clases anónimas) para crear un Handler, el objeto Handler mantiene implícitamente una referencia a la Activity.

Mientras tanto, el Handler generalmente se acompaña de un hilo de fondo que consume mucho tiempo, y este hilo de fondo envía mensajes para actualizar la UI una vez que se completa la tarea. Sin embargo, si el usuario cierra la Activity durante el proceso de solicitud de red, en condiciones normales, la Activity ya no se utiliza y podría ser reciclada durante la comprobación de GC, pero debido a que en este momento el hilo aún no se ha completado, y este hilo mantiene una referencia al Handler (de lo contrario, ¿cómo podría enviar mensajes al Handler?), este Handler también mantiene una referencia a la Activity, lo que hace que la Activity no pueda ser reciclada (es decir, fugas de memoria), hasta que finalice la solicitud de red.

Además, si se ejecuta el método postDelayed() de Handler, se tendrá una MessageQueue antes de que llegue el delay establecido. -Message -Handler -La cadena de Activity puede llevar a que tu Activity sea referenciada y no pueda ser reciclada.

Una de las soluciones es usar referencias débiles:

static class MyHandler extends Handler {
  WeakReference<Activity > mActivityReference;
  MyHandler(Activity activity) {
    mActivityReference= new WeakReference<Activity>(activity);
  }
  @Override
  public void handleMessage(Message msg) {
    final Activity activity = mActivityReference.get();
    if (activity != null) {
      mImageView.setImageBitmap(mBitmap);
    }
  }
}

Aquí está la recopilación de información sobre el mecanismo de mensajería de handler de Android, se continuará complementando información relevante, ¡gracias a todos por el apoyo a este sitio!

Declaración: El contenido de este artículo se obtiene de la red, pertenece al autor original, el contenido se contribuye y carga voluntariamente por los usuarios de Internet, este sitio web no posee los derechos de propiedad, no ha sido editado artificialmente y no asume responsabilidad alguna por la responsabilidad legal. Si encuentra contenido sospechoso de violación de derechos de autor, le invitamos a enviar un correo electrónico a: notice#w3Aviso: El contenido de este artículo se obtiene de la red, pertenece al autor original, el contenido se contribuye y carga voluntariamente por los usuarios de Internet, este sitio web no posee los derechos de propiedad, no ha sido editado artificialmente y no asume responsabilidad alguna por la responsabilidad legal. Si encuentra contenido sospechoso de violación de derechos de autor, le invitamos a enviar un correo electrónico a: notice#w

Te gustará