Análisis del controlador del mecanismo de mensajes de Android

Prefacio

El mecanismo de mensajes de Android se refiere principalmente al mecanismo de operación de Handler. La operación de Handler necesita el soporte de MessageQueue y Looper subyacentes. Handler es la interfaz superior del mecanismo de mensajes de Android, lo que hace que solo sea necesario interactuar con Handler durante el proceso de desarrollo. A través del Handler, una tarea se puede cambiar al subproceso donde se encuentra el Handler para su ejecución.

Componentes del mecanismo

El análisis del principio del mecanismo Handler debe involucrar clases importantes como ActivityThread, Handler, MessageQueue, Looper y Message. Una breve introducción a estas clases:


  • La entrada de inicio del programa ActivityThread , este es el hilo principal (hilo de la interfaz de usuario). Looper se inicializa cuando se crea ActivityThread, por lo que Handler se puede utilizar de forma predeterminada en el hilo principal. Nota: El hilo no tiene un Looper por defecto. Si necesita usar un Handler, debe crear un Looper para el hilo.
  • La
    cola de mensajes de MessageQueue almacena un grupo de mensajes internamente y proporciona trabajo de inserción y eliminación en forma de cola. La estructura de almacenamiento interno no es una cola real, y la estructura de matriz de una lista enlazada individualmente se usa para almacenar la lista de mensajes.

  • Bucle de mensajes de Looper , debido a que MessageQueue es solo una unidad de almacenamiento de mensajes, no puede procesar mensajes, y Looper buscará nuevos mensajes en un bucle infinito y procesará los mensajes si los hay; de lo contrario, esperará para siempre.
  • ThreadLocal
    no es un hilo y su función es almacenar datos en cada hilo. ThreadLocal puede almacenar y proporcionar datos en diferentes subprocesos sin interferir entre sí, y el Looper de cada subproceso se puede obtener a través de ThreadLocal.

En el mecanismo de mensajes de Android, la relación operativa de cada parte del controlador es la siguiente:
Diagrama del mecanismo del controlador

ActivityThread

La entrada de la aplicación es en el método principal de ActivityThread, el siguiente es el análisis del código fuente

public static void main(String[] args) {
    
    
    ......
    //1 创建Looper 和 MessageQueue
    Looper.prepareMainLooper();
      //2 建立与AMS的通信
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
    
    
        sMainThreadHandler = thread.getHandler();
    }
    ......
    //3 无限循环,不断取出消息,向Handler分发
    Looper.loop();
    
    //可以看出来主线程也是在无限的循环的,
    //异常退出循环的时候会报错. 
    throw new RuntimeException("Main thread loop 
    unexpectedly exited");
}

Debemos saber que si el programa no tiene un bucle infinito, saldrá inmediatamente después de ejecutar la función principal. La razón por la que nuestra APLICACIÓN puede seguir ejecutándose es porque Looper.loop () es un bucle infinito.

Looper 和 ThreadLocal

1. Análisis de clases de looper

public final class Looper {
    
    
    // 每个线程都有一个ThreadLocal,用来保存Looper对象
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class
    // 保存消息队列
    final MessageQueue mQueue;
    // 保存线程
    final Thread mThread;
    ......
    public static void prepare() {
    
    
        prepare(true);
    }
    
    //prepare 函数 
    private static void prepare(boolean quitAllowed) {
    
    
        //判断sThreadLocal.get()是否为空,如果不为空说明已经为该线程设Looper,不能重复设置。
        if (sThreadLocal.get() != null) {
    
    
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //如果sThreadLocal.get()为空,说明还没有为该线程设置Looper,那么创建Looper并设置
        sThreadLocal.set(new Looper(quitAllowed));
    }
    //ActivityThread 调用Looper.prepareMainLooper();该函数调用prepare(false);
    public static void prepareMainLooper() {
    
    
        prepare(false);
        synchronized (Looper.class) {
    
    
            if (sMainLooper != null) {
    
    
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    public static Looper getMainLooper() {
    
    
        synchronized (Looper.class) {
    
    
            return sMainLooper;
        }
    }
    // 获取当前线程Looper 
    public static @Nullable Looper myLooper() {
    
    
        return sThreadLocal.get();
    }
    ......
    public static void loop() {
    
    
    //得到Looper
    final Looper me = myLooper();
    if (me == null) {
    
    
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //得到MessageQueue
    final MessageQueue queue = me.mQueue;
    ......
    for (;;) {
    
    //无限循环
        Message msg = queue.next(); // 取下一个Message 可能阻塞在这里
        if (msg == null) {
    
    
            //如果队列为空直接return直接结束了该方法,即循环结束
            return;
        }
        ......
        try {
    
    
            //分发message (target指handler)
            msg.target.dispatchMessage(msg);
            ......
        } finally {
    
    
        }
        ......
    }
}

Se puede ver en el código fuente que el MessageQueue interno se eliminará en el método Looper.loop (), y los mensajes en la cola de mensajes se iterarán y los mensajes se distribuirán de acuerdo con el destino del mensaje ( se enviará al método handleMessage en la clase Handler). Al mismo tiempo, el bucle for () en el código fuente es un bucle infinito ¿Por qué no hace que la aplicación se congele?

Un hilo es un fragmento de código ejecutable.Cuando se ejecuta el código ejecutable, el ciclo de vida del hilo debe terminarse y el hilo sale. En cuanto al hilo principal, nunca queremos que se ejecute durante un período de tiempo y luego salir por nosotros mismos, entonces, ¿cómo podemos asegurarnos de que siempre pueda sobrevivir? El método simple es que el código ejecutable se puede ejecutar para siempre, y el bucle sin fin puede garantizar que no se saldrá. Por ejemplo, el subproceso de enlace también utiliza el método de bucle sin fin y las operaciones de lectura y escritura con el controlador de Binder a través de un método de bucle diferente Por supuesto, no es simplemente bucle sin fin, dormir cuando no hay mensaje .

Pero esto puede plantear otra pregunta, ya que es un bucle sin fin, ¿cómo lidiar con otros asuntos?

Creando nuevos hilos. La operación que realmente congelará el hilo principal es que el tiempo de operación del método de devolución de llamada onCreate / onStart / onResume es demasiado largo, lo que provocará una caída de fotogramas, o incluso ANR, looper.loop en sí no hará que la aplicación se congele.

¿El bucle infinito del hilo principal que se ejecuta todo el tiempo consume recursos de la CPU?

De hecho, este no es el caso, aquí está el mecanismo pipe / epoll de Linux. En pocas palabras, cuando no hay ningún mensaje en el MessageQueue del hilo principal, se bloquea en el método nativePollOnce () en el bucle queue.next (). En este momento, el subproceso principal liberará la CPU. El recurso entra en estado inactivo hasta que llega el siguiente mensaje o se produce una transacción, y el subproceso principal se activa 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 múltiples descriptores al mismo tiempo. Cuando un descriptor está listo (listo para lectura o escritura), el programa correspondiente es notificado inmediatamente para realizar operaciones de lectura o escritura, esencialmente E / S síncronas , es decir, la lectura y la escritura están bloqueadas. Por lo tanto, el hilo principal está en su mayor parte inactivo y no consume muchos recursos de CPU.

2. Análisis de clases ThreadLocal

ThreadLocal es una clase de almacenamiento de datos dentro de un subproceso, a través de la cual los datos se pueden almacenar en un subproceso específico. Después de que se almacenan los datos, los datos almacenados solo se pueden obtener en el subproceso especificado y los datos no se pueden obtener para otros subprocesos.

public class ThreadLocal<T> {
    
    
     ....
      public void set(T value) {
    
    
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
     
      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();
    }
     ....
}

Se puede ver en los métodos set y get de ThreadLocal que los objetos en los que operan son las matrices de tabla del objeto localValues ​​del hilo actual, por lo que se accede a los métodos set y get del mismo ThreadLocal en distintos hilos, y sus operaciones de lectura y escritura en ThreadLocal Limitadas al interior del hilo respectivo , esto es que ThreadLocal puede almacenar y modificar datos en varios hilos sin interferir entre sí.

3. Relación entre Thread, ThreadLocal y Looper

Looper 类源码

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// prepare() 方法中Looper 对象存储到ThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
-----------------------------------------------------------------------------------
ThreadLocal 类源码

public void set(T value) {
    
    
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
//这是ThreadLocal的getMap方法
ThreadLocalMap getMap(Thread t) {
    
    
    return t.threadLocals;
}

Resumen: A partir del análisis del código fuente anterior, podemos ver que map.set (this, value) se coloca a sí mismo (ThreadLocal) y el valor (Looper) en un mapa. Si pones otro, se sobrescribirá, porque el mapa no Permitir pares clave-valor. Las claves de entrada se repiten. Por lo tanto, ThreadLocal puede vincular Thread y Looper mediante los métodos get () y set ().

Cola de mensajes

MessageQueue contiene principalmente 2 operaciones: insertar y leer. Los métodos correspondientes para insertar y leer son enqueueMessage y next. La función de enqueueMessage es insertar un mensaje en la cola de mensajes, y la función de next es tomar un mensaje de la cola de mensajes y eliminarlo de la cola de mensajes. La implementación interna de la cola de mensajes es mantener la lista de mensajes a través de la estructura de datos de una lista enlazada individualmente. La lista enlazada individualmente tiene ventajas en la inserción y eliminación.

boolean enqueueMessage(Message msg, long when) {
    
    
      ......
    synchronized (this) {
    
    
       ......
        msg.when = when;
        Message p = mMessages;
        //检测当前头指针是否为空(队列为空)或者没有设置when 或者设置的when比头指针的when要前
        if (p == null || when == 0 || when < p.when) {
    
    
            //插入队列头部,并且唤醒线程处理msg
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
    
    
           // 几种情况要唤醒线程处理消息:1)队列是堵塞的 2)barrier,头部结点无target 3)当前msg是堵塞的
            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; // 将当前msg插入第一个比其when值大的结点前。
            prev.next = msg;
        }
        //调用Native方法进行底层操作,在这里把那个沉睡的主线程唤醒
        if (needWake) {
    
    
            nativeWake(mPtr);
        }
    }
    return true;
}

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 {
    
    
                        // 获取消息,并把消息队列移动到下一条消息
                        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;
                }
                ......
}

Análisis de manejadores

El trabajo del manejador incluye principalmente el proceso de enviar y recibir mensajes. Una serie de métodos de envío se realizan finalmente a través de una serie de métodos de envío. El proceso del controlador que envía un mensaje es insertar un mensaje en la cola de mensajes. El siguiente método de MessageQueue devolverá este mensaje a Looper, y Looper comenzará a procesar después de recibir el mensaje, y finalmente el mensaje será procesado por Looper por Handler, es decir, se llamará al método discochMessage de Handler, y luego el controlador entrará el proceso de procesamiento del mensaje. etapa.


public void dispathchMessage(Message msg){
    
    
    // 检查Message的callback是否为null,不为null就通过handleCallback来处理消息
    // Message的callback是一个Runnable对象,实际上就是handler的post方法所传递Runnable参数
    if(msg.callback!=null){
    
    
        handleCallback(msg);
    }else{
    
    
    // 检查Handler 的mCallback是否为null,不为null就调用mCallback的
    // handleMessage
        if(mCallback!=null){
    
    
            if(mCallback.handleMessage(msg))
            return;
        }
        handleMessage(msg);
    }
}

para resumir

Un Runnable se entrega al Looper dentro del controlador a través del método de publicación del controlador para su procesamiento, o se puede enviar un mensaje a través del controlador, y este mensaje también se procesará en el Looper. De hecho, el método de publicación finalmente se completa a través del método de envío. Cuando se llama al método de envío del controlador, llamará al método enqueueMessage de MessageQueue para poner el mensaje en la cola de mensajes, y luego, cuando Looper encuentre un nuevo mensaje, lo procesará y finalmente el Runnable en el mensaje. o se llamará al método handleMessage del controlador. Tenga en cuenta que Looper se ejecuta en el subproceso donde se crea el controlador, por lo que la lógica empresarial en el controlador se cambia al subproceso donde se crea el controlador para su ejecución.

referencia

Tour de análisis de código fuente de Android 3.1-mecanismo de mensajes análisis de código fuente
mecanismo de mensajes de Android-¿realmente conoces a Handler?
Análisis paso a paso del mecanismo Handler de Android

Supongo que te gusta

Origin blog.csdn.net/xufei5789651/article/details/106859142
Recomendado
Clasificación