Análisis de código fuente: mecanismo de controlador

1. Cuatro componentes principales

  • mensaje: mensaje.

  • MessageQueue: La cola de mensajes es responsable del almacenamiento y administración de mensajes, y es responsable de administrar el Mensaje enviado por el Manejador. La lectura eliminará automáticamente los mensajes, y existen ventajas en el mantenimiento, la inserción y la eliminación de listas de enlaces individuales. En su método next(), habrá un ciclo infinito, juzgando constantemente si hay un mensaje y, si lo hay, devolver el mensaje y eliminarlo.

  • Looper: Looper de mensajes, responsable de la distribución de hilos y mensajes asociados. Bajo este hilo, el mensaje se obtiene de MessageQueue y se distribuye al controlador. Cuando se crea el Looper, se creará un MessageQueue. Cuando el método loop() se llama, el bucle de mensajes comienza y continuará. Llame al método next() de messageQueue, procéselo cuando haya un mensaje; de ​​lo contrario, bloquee el método next() de messageQueue. Cuando se llama a quit() de Looper, se llamará a quit() de messageQueue En este momento, next() devolverá nulo, y luego el método loop() también saldrá.

  • Manejador: Procesador de mensajes, responsable de enviar y procesar mensajes, enfrentar a los desarrolladores, proporcionar API y ocultar los detalles detrás de la implementación.

2. El proceso de circulación del mensaje

  1.  El controlador envía el mensaje Message a la cola de mensajes MessageQueue a través de sendMessage().
  2. Looper extrae continuamente el mensaje de la condición de activación a través de loop() y entrega el mensaje al controlador de destino correspondiente para su procesamiento.
  3. El controlador de destino llama a su propio método handleMessage() para procesar el mensaje.

3. ¿Cuántos Loopers tiene un hilo? ¿Cómo garantizar?

        Un subproceso tiene solo un Looper, y Looper.loop() es iniciado por el subproceso

        Looper en el subproceso principal se crea en la función principal de ActivityThread en AMS, llame a Looper.prepareMainLooper(); y luego llame a prepare(), que tiene un sThreadLocal final estático para configurar un Looper (el constructor del objeto Looper es un objeto privado). , solo se puede crear internamente), el ThreadLocalMap de ThreadLocal usa el hilo actual como la clave, y el Looper creado se almacena como un par de valores clave-valor, y sThreadLocal hará una prueba antes de configurar, y si ThreadLocalMap tiene un valor, se lanzará una excepción, por lo que se garantiza que solo hay un Looper en un hilo

 1. Looper en el hilo principal se crea en la función principal de ActivityThread en AMS

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

2. Llame a prepare() en Looper.prepareMainLooper()

3. En el método de preparación, vaya a un nuevo constructor de Looper privado para crear un objeto Looper

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

4. Establezca Looper como valor y el hilo actual como clave para ThreadLocalMap mediante el método set

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

5. Dado que se llama en el subproceso principal, el subproceso principal y Looper se asocian a través de la relación entre el par clave-valor ThreadLocalMap 

6. sThreadLocal es modificado por static final, que es único en toda la aplicación, y verifica si sThreadLocal está vacío en el método de preparación, y arroja una excepción directamente si no está vacío. Un Looper solo se puede crear una vez con un hilo

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

7. Esto asegura la correspondencia uno a uno entre hilos y Looper

4. ¿Cuántos manejadores tiene un hilo?

Se pueden crear innumerables Handlers, pero las colas de mensajes que utilizan son las mismas, es decir, el mismo Looper

5. ¿Cuál es el motivo de la fuga de memoria del controlador? ¿Por qué otras clases internas no tienen este problema?

        Cree un controlador de una clase interna, que es equivalente a una clase interna anónima. El controlador pasa el mensaje a MessageQueue a través de enqueueMessage() a través de envío o publicación, y MessageQueue retendrá el mensaje. En el método enqueueMessage, esto será asignado al destino de msg. El mensaje contiene el controlador, y el controlador es una clase interna anónima que contiene el objeto de la clase externa, es decir, la actividad. El mensaje no se ha ejecutado y siempre ha existido en MessageQueue, por lo que la actividad no se puede destruir y los objetos dentro no se procesarán, lo que provocará una pérdida de memoria 

Solución:        

  • Declare el controlador como una clase estática y use referencias débiles para contener el controlador (es posible que no se encuentre la actividad al usarla)
  •  Destruye todos los mensajes en la destrucción de la actividad. 

6. ¿Por qué se puede usar un nuevo controlador en el subproceso principal y cómo operar en el subproceso?

        Debido a que se ha llamado a Looper.prepareMainLooper() en la función principal de ActivityThread, la inicialización de Looper se completa, por lo que el nuevo controlador puede estar directamente en el hilo principal. 

7. ¿Cuál es la solución para el looper que se mantiene en el subproceso y cuando no hay ningún mensaje en la cola de mensajes? ¿Cuál es el uso? ¿Qué pasa con el hilo principal?

         El subproceso secundario está en el bucle for infinite, la llamada al método nativePollOnce está en estado de bloque, lo que significa que Looper.loop() está en estado de bloque y se ha estado ejecutando, lo que hace que el método de ejecución siempre se ejecute, lo que significa que el el subproceso secundario siempre se está ejecutando, relacionado con el subproceso La memoria no se liberará, lo que provocará una fuga de memoria

        Solución:

               Llame a looper.quitSafely() -> MessageQueue.quit -> MessageQueue#removeAllMessagesLocked: elimine todos los mensajes y llame a nativeWake para activar el bucle for infinite, ejecute hasta que msg == null, luego salga del bucle for y el hilo se cerrará arriba

        El hilo principal no puede salir

MessageQueue#enqueueMessage guarda mensajes

 boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

MessageQueue#quit eliminar mensaje, despertar


    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

MessageQueue#siguiente código clave para obtener mensajes

  Message next() {
        ……

        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
              ……
    }

8. Dado que se pueden almacenar varios controladores para agregar datos a MessageQueue, y cada controlador puede estar en un subproceso diferente al enviar un mensaje, ¿cómo garantiza la seguridad del subproceso internamente? ¿Qué pasa con las noticias?

        MessageQueue se inicializa con final y no se puede modificar después de la inicialización. MessageQueue solo puede ser creado por Looper. Los subprocesos corresponden a Looper uno a uno, y Looper corresponde a MessageQueue uno a uno. Sincronizado (esto) bloquea el único MessageQueue para garantizar la seguridad de subprocesos, lo mismo se aplica a la obtención de mensajes, lo que impide agregar mensajes al obtener mensajes. Se garantiza que el acceso a los mensajes es único. 

9. ¿Cómo crear cuando se usa un mensaje?

        La cantidad de mensajes es grande, los nuevos mensajes directos darán lugar a la creación y destrucción frecuentes, GC frecuentes, y cada GC traerá problemas STW (detener todos los subprocesos), lo que resultará en atascos y fragmentación de la memoria, lo que resultará en OOM.

        Utilice la función de obtención () para crear, utilizar el modo de peso ligero, reutilizar el mensaje y reducir el consumo de memoria:

  • A través del método estático Message.obtain() de Message;

  • A través del método público handler.obtainMessage() de Handler.

    private static int sPoolSize = 0;
    Message next;
    private static final Object sPoolSync = new Object();
    private static Message sPool;

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

10. ¿Cómo se realiza el bloqueo de mensajes del handler?¿Por qué el hilo principal no bloquea?

       Involucrando el mecanismo pipe/epoll de Linux, simplemente hablando, cuando el MessageQueue del hilo principal no tiene noticias, será bloqueado en el método nativePollOnce() en el queue.next() del bucle. liberará los recursos de la CPU y entrará en el estado inactivo.Hasta que llegue el siguiente mensaje o se produzca una transacción, despierte el subproceso principal para que funcione escribiendo datos en el extremo de escritura de la canalización. El mecanismo epoll utilizado aquí es un mecanismo de multiplexación de E/S que puede monitorear varios descriptores al mismo tiempo. Cuando un descriptor está listo (listo para leer o escribir), inmediatamente notifica al programa correspondiente para leer o escribir. La esencia es la E/S sincrónica. , es decir, se bloquea la lectura y la escritura. Por lo tanto, el subproceso principal está inactivo la mayor parte del tiempo y no consume muchos recursos de la CPU.

        Cada evento es un mensaje, incluido el servicio, el privado, la entrada, el botón, el evento de clic, la transmisión, etc., y finalmente es administrado por el controlador. El controlador bloqueará si no hay procesamiento de mensajes. Cuando el hilo principal está bloqueado, el el subproceso principal no tiene nada que hacer, dormir, cuando se genera un mensaje, enqueueMessage un mensaje en MessageQueue, y luego llamar nativeWake para despertar el Looper en espera.Después de despertar, queue.next se despierta en este momento 

         Y ANR significa que después de enviar un mensaje, el mensaje no se procesa a tiempo y el tiempo que consume excede el límite (no hay respuesta a los eventos de entrada dentro de los 5 segundos, como botones, toques de pantalla, etc.; la transmisión no se completa dentro de los 10 segundos), aparecerá ANR y se informará un ANR. , encapsulado en un mensaje, envíelo al controlador para que lo procese y active un cuadro de diálogo 

        Mecanismo de gestión de MGA:

                Cada aplicación existe en su propia máquina virtual, es decir, tiene su propia función principal.

                Proceso de inicio: lanzador -> cigoto -> aplicación de arte -> subproceso de actividad -> principal

                Todas las funciones del ciclo de vida de todas las aplicaciones (incluidas la actividad y el servicio) se ejecutan en este Looper y existen en forma de mensaje

Resumir:

        La aplicación atascada ANR no tiene nada que ver con este Looper en absoluto. Cuando la aplicación no tiene procesamiento de mensajes, está durmiendo y libera el hilo; atascado es ANR, y el bloqueo de mensajes es dormir

Once, la postura correcta de tocar Tostadas en el subhilo

En esencia, debido a que la implementación de Toast depende de Handler, se puede modificar de acuerdo con los requisitos de los subprocesos que usan Handler. Lo mismo ocurre con Dialog.

12. ¿Cómo se realiza el delay del handler postDelay?

handler.postDelay no espera un cierto período de tiempo antes de colocarlo en MessageQueue, sino que ingresa directamente a MessageQueue, que se realiza combinando el orden cronológico y la activación de MessageQueue.

Supongo que te gusta

Origin blog.csdn.net/weixin_42277946/article/details/130308898
Recomendado
Clasificación