El mecanismo del controlador de aprendizaje del código fuente de Android y sus seis puntos centrales

Prefacio:

Este artículo pertenece al artículo en la columna de exploración del código fuente de Android. La lista de todos los artículos en la columna está vinculada a continuación. Bienvenido a leer.

Exploración del código fuente de Android icono-predeterminado.png?t=M1L8https://blog.csdn.net/rzleilei/category_6506586.html?spm=1001.2014.3001.5482

1. Introducción al mecanismo Handler

1.1 ¿Qué es el controlador?

El controlador es un mecanismo muy utilizado en Android, que se utiliza principalmente para la comunicación entre subprocesos.

1.2 ¿Por qué debería entender el principio del controlador?

Estaba chateando en un grupo antes y dije que a menudo se preguntaba a los controladores en las entrevistas, y luego me rociaron, diciendo que ahora son MVVM, ViewBinding y otras arquitecturas, las que todavía usan controladores. De hecho, ahora hay más y más marcos encapsulados en Android, lo que hace que sea cada vez más fácil de usar para nosotros, y hay cada vez menos escenarios para usar Handler. Pero menos uso no significa que sea inútil. Estos marcos existentes, e incluso los marcos futuros previsibles, se basan en el mecanismo del controlador.

Por ejemplo, nosotros, la gente común, manejamos, tal vez mientras sepamos conducir, no necesitamos entender el principio del automóvil. Pero si quieres ser un piloto de carreras, aún necesitas tener cierta comprensión de la mecánica y los principios de cómo funciona un automóvil.

Finalmente, ¿cuáles son los beneficios de comprender el principio del controlador?

1. El mecanismo del controlador es muy clásico y también se puede usar según la escena en el proceso de escritura del código.

2. De hecho, además de los mensajes síncronos ordinarios, los handlers también incluyen mensajes de barrera y mensajes asíncronos, que también podemos utilizar según la escena.

3.handler también proporciona el mecanismo inactivo IdelHandler.

4. El controlador puede ayudarnos a averiguar dónde el código está provocando la congelación.

Los puntos anteriores se explicarán en detalle en los siguientes artículos.

1.3 ¿Cuál es el papel de Handler en la entrevista?

Al mismo tiempo, Handler también es un mecanismo de Android que a menudo se pregunta en las entrevistas.

Llegué a la conclusión de que hay unos seis puntos que se preguntarán con frecuencia. Estos seis puntos técnicos están especialmente marcados en rojo y pertenecen a los puntos técnicos que son fáciles de preguntar en la entrevista.

Por ejemplo: punto técnico 1

En segundo lugar , la descripción general del principio del controlador

Como se muestra en la siguiente figura: el mecanismo de Handler en Android es obtener un objeto Handler en el subproceso secundario y enviar tareas a través de este objeto Handler como MessageQueue. Se abre un bucle infinito infinito en el subproceso principal y la tarea Message se recupera continuamente de MessageQueue. Si se recupera, se ejecuta la tarea en Messagez. Si no se recupera, entra en estado de suspensión.

 Varios objetos importantes involucrados

Manejador: Generalmente creado en el hilo principal, sosteniendo un Looper del hilo principal, responsable de la distribución del mensaje al hilo principal.

Mensaje: Puede entenderse como tarea, la unidad de ejecución de la tarea.

MessageQueue: dentro hay una lista enlazada individualmente, que almacena tareas de mensajes.

Looper: Encargado de procesar las tareas de Message, y su método loop abre un loop infinito en el hilo principal.

3. El proceso de agregar Mensaje

3.1 Añadir la escritura de Meesage

Podemos ver muchas formas de agregar un mensaje, pero al final, se llama al método sendMessageDelayed.

La ortografía general es la siguiente:

Método de escritura 1: sendMessageDelayed(Mensaje msg, long delayMillis)

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

Método de escritura 2: enviar mensaje (mensaje de mensaje)

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

De hecho, se llama al método sendMessageDelayed.

Escribir tres:

mHandler.post(new Runnable() {
                @Override
                public void run() {
                   //do something 
                }
            });

En realidad, entremos y echemos un vistazo.

sendMessageDelayed(getPostMessage(r), 0);

Mire el método getPostMessage nuevamente:

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

Todo bien. De hecho, también es para crear un mensaje y luego enviarlo.

3.2 enviarMensajeDemorado方法

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

El segundo parámetro que se pasa al llamar a sendMessageAtTime es el tiempo de ejecución.

Este tiempo de ejecución se calcula a partir del tiempo actual + el tiempo de retraso.

Aquí hay un punto técnico 1: ¿La modificación de la hora del teléfono móvil afectará la ejecución del mensaje? El uso actual es SystemClock.uptimeMillis(), que se refiere al tiempo de suspensión no profunda del sistema después de encender el teléfono móvil. , no el tiempo del teléfono móvil. Por lo tanto, modificar la hora actual en el teléfono móvil no afectará la ejecución del Mensaje. En general, también podemos usar este valor para saber cuánto tiempo está encendido el teléfono.

3.3 enviarMensaje a la Hora

1. Se realiza una evaluación de MessageQueue en sendMessageAtTime.

 public boolean sendMessageAtTime(@NonNull 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);
    }

3.4 método enqueueMessage

  private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

Agregar mensaje a la cola

3.5 método enqueueMessage

1. El código aquí se ve demasiado y desordenado. Pero la lógica central es insertar el nodo de mensaje actual en la lista enlazada individualmente de acuerdo con la latitud del tiempo de ejecución de adelante hacia atrás. Punto técnico 2: ¿De qué manera está ordenada la lista enlazada? secuencia de tiempo de ejecución

2. Hay un parámetro cuando está en Mensaje, que se asignará cuando se agregue a la lista vinculada, y se registre el tiempo de ejecución.

3. Al mismo tiempo, despierta el sueño a través de nativeWake. Por qué hibernar, hablaremos en el cuarto capítulo a continuación, generalmente cuando no hay un mensaje para ejecutar, entrará en hibernación para liberar recursos de la CPU.

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

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

En este punto, el mensaje se inserta en MessageQueue y se completa el proceso de inserción.

En cuarto lugar, el proceso de ejecución del mensaje

4.1 Llame a Looper.prepare() para enlazar con el hilo actual.

Generalmente, no necesitamos llamar al método de preparación, porque después de que se inicia la aplicación, en el método principal de ActivityThread, se ha llamado al método Looper.prepare.

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

La lógica central de la preparación es enlazar con el subproceso actual a través de ThreadLocal. Se garantiza que un Looper solo se vinculará a un solo hilo.

4.2 Looper.loop inicia un bucle infinito

Llame al método Looper.loop para iniciar un bucle infinito. Aquí se realiza una verificación, y si Looper no está vinculado al hilo actual, se lanzará una excepción.

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        ...不重要代码忽略

        for (;;) { //启动无限循环
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }

4.3 Obtener el mensaje e intentar ejecutarlo

1. Llamará a MessageQueue.next() para intentar obtener el mensaje, si no puede obtenerlo, se bloqueará. Lo analizaremos en el próximo capítulo.

2. Si no se obtiene y devuelve el mensaje, la aplicación está en estado de salida, por lo que el ciclo también se cierra.

3.msg.target.dispatchMessage() se entrega al controlador para procesar el mensaje. Dado que el subproceso de ejecución actual es el subproceso principal, la devolución de llamada ejecutada en dispatchMessage también se encuentra en el subproceso principal.

4. Podemos ver que hay un registro en el código para la impresión de entrada, lo que es muy útil para nuestro control del rendimiento, que explicaremos en el Capítulo 7.

5.msg.recycleUnChecked marca que msg se ha utilizado y entra en un estado reutilizable.

4.4 dispatchMessage ejecuta el mensaje

Hay dos formas de devolver la llamada,

El método 1 ejecuta directamente la devolución de llamada en el mensaje;

La segunda forma es personalizar el controlador y anular su método handleMessage.

Aquí podemos ver claramente que la prioridad de CallBack será mayor. Este es el punto técnico 3: cuál de CallBack y handleMessage se ejecutará primero

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

En handleCallBack, es directamente a través de

message.callback.run();

Ejecute la tarea ejecutable de Message. PD: run() es un método definido en la interfaz Runnable

Cinco, MessageQueue.next() para obtener el mensaje

Mire primero el código, que se divide en los siguientes enlaces.

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

            nativePollOnce(ptr, nextPollTimeoutMillis);//4.1 nativePollOnece

            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) {//4.2 寻找可用message
                    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;
                }

               ...idelHandler相关代码先忽略
        }
    }

5.1 método nativePollOnce

Este es un método nativo, el bloqueo. La suspensión de la CPU que mencionamos anteriormente también se logra a través del mecanismo de nativePollOnce. Cuando se llama a este método, los recursos de la CPU se liberarán hasta que el mundo exterior lo despierte. El principio de implementación subyacente corresponde al mecanismo epoll de Linux, y no lo analizaremos en detalle en este artículo. Solo necesitamos saber la función de su método.

nativePollOnce tiene dos parámetros, ptr y timeoutMillis

ptr puede entenderse como un valor único de una etiqueta nativa, que se utiliza para identificar el hilo enlazado.

timeoutMillis es el período de tiempo de espera. Principalmente dividido en tres tipos -1, 0, > 0.

=-1 está en un estado de bloqueo infinito a menos que el mundo exterior lo despierte.

Cuando =0 , no se bloqueará y se ejecutará inmediatamente.

>0 bloqueará el tiempo correspondiente y luego liberará el estado de bloqueo.

PD: La implementación nativa de nativePollOnce es en realidad algo similar a la capa de Android y también tiene una cola circular de bloqueo. El mecanismo de implementación subyacente es epoll. Dado que la capa nativa no es el núcleo de este artículo, no ampliaré la introducción aquí. Los estudiantes interesados ​​pueden dejar un mensaje.

5.2 Recorra la lista enlazada para encontrar mensajes disponibles

1. Debido a que la lista enlazada se inserta de acuerdo con el tiempo de ejecución, el primer Mensaje ejecutado debe estar en la parte superior de la lista enlazada;

2. Primero obtenga el tiempo de estado de no suspensión del sistema actual;

3. Primero intente obtener el encabezado de la lista vinculada, luego no hay datos en la lista vinculada. Luego asigne nextPollTimeoutMillis a -1, y en el siguiente ciclo, entrará en un estado de bloqueo infinito y se despertará directamente. Esto corresponde al mecanismo de activación nativo mencionado en el Capítulo 2.5 .

4. Si el nodo principal no está vacío, compare su tiempo de ejecución con el tiempo actual;

5. Si su tiempo de ejecución es menor que el tiempo actual, calcule la diferencia nextPollTimeoutMillis. Y saltará de este proceso de selección de mensajes. Y en el siguiente bucle, nativePollOnce utilizará este valor para dormir durante el tiempo correspondiente. Se garantiza que cuando llega el tiempo de suspensión, es solo el tiempo de ejecución del nodo principal.

6. Si su tiempo de ejecución es mayor al tiempo actual, indica que el nodo puede ser ejecutado. Cambie el nodo principal al nodo del siguiente nivel. Y marque el Mensaje actual ha sido utilizado por Message.markInUse.

7. Regrese al objeto de mensaje encontrado en el paso anterior

5. Mensaje asíncrono/mensaje de barrera

Punto técnico 4: Mecanismo de implementación de mensajes asíncronos de barrera

1 El mensaje de barrera es en realidad un mensaje de mensaje con un objetivo vacío.

2 Los mensajes asíncronos deben usarse con mensajes de barrera.

3 Si el nodo principal es un mensaje de barrera, buscará mensajes asincrónicos de adelante hacia atrás en la lista vinculada de mensajes y saltará del bucle cuando encuentre un mensaje asincrónico.

4 Y elimine el nodo asíncrono actual de la lista vinculada y asocie los dos nodos antes y después del nodo asíncrono.

5 Cabe señalar aquí que el nodo principal sigue siendo el mensaje de barrera y no se ha eliminado. Así que los mensajes normales aún no se ejecutan.

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;//把异步消息的next赋值给前面那个节点的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;
                }

6. Controlador inactivo

Punto técnico 5: tiempo de ejecución de IdelHandler

6.1 Introducción a IdleHandler

Como su nombre indica, es una tarea de tiempo libre. Cuando actualmente no hay ningún mensaje para ejecutar, se activará la ejecución de IdelHandler. Generalmente, podemos poner tareas que deben ejecutarse en el subproceso principal pero con baja prioridad en IdelHandler para su ejecución, como precargar la página siguiente, cargar la segunda pantalla, etc. Espere.

6.2 ¿Cuándo se activa IdleHandler?

Mirando el código, puedo saber que cuando se obtiene el Mensaje, si no se obtiene el Mensaje, ingresará al proceso de ejecución del IdleHandler.

Message next() {
       ...代码省略
        for (;;) {
            
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;  //如果取到了消息则返回
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                ...
                //如果没有取到消息,则会执行下面的逻辑
                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

6.3 Proceso de ejecución de IdleHandler

Puede haber múltiples IdleHandlers, que mIdelHandlers almacenan en MeesageQueue y se convierten en matrices y se ejecutan en secuencia cada vez que se ejecutan.

 pendingIdleHandlerCount = mIdleHandlers.size(); 
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
 for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

Cabe señalar aquí que la devolución de llamada de IdleHandler solo se ejecutará una vez y se eliminará una vez que se complete la ejecución.

Siete, monitoreo de ejecución de mensajes

Punto técnico 6 : Cómo monitorear el problema de bloqueo del hilo principal

7.1 Principio de activación

En el capítulo 3.3, mencionamos el objeto de registro. Miremos hacia atrás en el código, y aquí podemos ver claramente que se llamará al registro antes y después de que se ejecute el mensaje. El tiempo entre estas dos impresiones se puede considerar como el tiempo de ejecución de la devolución de llamada en el Mensaje.

private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
        ...省略代码
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " "
                    + msg.callback + ": " + msg.what);
        }
        ...省略代码
        try {
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
        ...省略代码
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

       
    }

7.2 Cómo usar

Echemos un vistazo a la clase de impresora correspondiente al registro, que en realidad es una interfaz.

public interface Printer {
    /**
     * Write a line of text to the output.  There is no need to terminate
     * the given string with a newline.
     */
    void println(String x);
}

Y descubrimos que el registro nos permite configurarlo activamente. Cuando se usa, se toma el objeto Looper.mLogging.

El método setMessageLogging simplemente establece mLogging.

 public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }

Entonces, solo necesitamos crear un objeto Printer y registrarlo en Looper, luego, a través del tiempo de dos devoluciones de llamada, podemos juzgar qué ejecución de Meesage se agotó. El siguiente código puede monitorear el escenario donde todos los mensajes del hilo principal se ejecutan durante más de 100 milisegundos.

//声明Printer对象
private Printer printer = new Printer() {
        @Override
        public void println(String it) {
            long currentTimeMillis = System.currentTimeMillis();
            //其实这里应该是一一对应判断的,但是由于是运行主线程中,所以Dispatching之后一定是Finished,依次执行
            if (it.contains("Dispatching")) {
                lastFrameTime = currentTimeMillis;
                return;
            }
            if (it.contains("Finished")) {
                long useTime = currentTimeMillis - lastFrameTime;
                //记录时间
                if (useTime > 100) {
                    //todo 要判断哪里耗时操作导致的
                    Log.i(TAG, "执行超过100毫秒");
                }
            }
        }
    };

//然后注册
 Looper mainLooper = Looper.getMainLooper();
 mainLooper.setMessageLogging(printer);

7.3 Escenarios de aplicación

Continuando con la expansión, definitivamente no es suficiente si solo sabemos si el hilo principal está atascado o no. También debemos preguntarnos ¿dónde está la tarjeta? Esto es también lo que BlockCanary quiere resolver. Sin embargo, aquí también tenemos una implementación simple, una clase puede completar la supervisión del rendimiento.

1. Podemos iniciar un subproceso secundario para capturar continuamente el estado de la pila del subproceso principal cada vez que se especifica (por ejemplo, 20 milisegundos).

2. Cuando la devolución de llamada de println notifica que se inicie la ejecución de la devolución de llamada del Mensaje, almacenamos cada pila capturada en el Mapa.

3. Cuando finaliza la notificación de devolución de llamada de println, juzgamos el tiempo de ejecución.Si se excede el tiempo de espera, se imprimirán todas las estructuras de pila en el Mapa. Si hay dos estructuras de pila idénticas en el Mapa, significa que el método correspondiente a esta pila se ha ejecutado durante al menos 20 milisegundos (hasta 40 milisegundos). Si hay 3, se ha ejecutado durante al menos 40 ms, y así sucesivamente.

4. Entonces, por la cantidad de veces que se imprime la misma pila, sabemos dónde se produce el atasco. Lo uso a menudo en la fase de desarrollo y depuración, y es muy fácil de usar.

Se adjunta el código completo de la pequeña clase de monitoreo de rendimiento:

package com.common.monitor;

import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;
import android.util.Printer;

import com.common.monitor.monitor.BaseMonitor;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class ANRMonitor{

    final static String TAG = "anr";

    public static void init(Context context) {
        if (true){//开关
            return;
        }
        ANRMonitor anrMonitor = new ANRMonitor();
        anrMonitor.start(context);
        Log.i(TAG, "ANRMonitor init");
    }

    private void start(Context context) {
        Looper mainLooper = Looper.getMainLooper();
        mainLooper.setMessageLogging(printer);
        HandlerThread handlerThread = new HandlerThread(ANRMonitor.class.getSimpleName());
        handlerThread.start();
        //时间较长,则记录堆栈
        threadHandler = new Handler(handlerThread.getLooper());
        mCurrentThread = Thread.currentThread();
    }

    private long lastFrameTime = 0L;
    private Handler threadHandler;
    private long mSampleInterval = 40;
    private Thread mCurrentThread;//主线程
    private final Map<String, String> mStackMap = new HashMap<>();

    private Printer printer = new Printer() {
        @Override
        public void println(String it) {
            long currentTimeMillis = System.currentTimeMillis();
            //其实这里应该是一一对应判断的,但是由于是运行主线程中,所以Dispatching之后一定是Finished,依次执行
            if (it.contains("Dispatching")) {
                lastFrameTime = currentTimeMillis;
                //开始进行记录
                threadHandler.postDelayed(mRunnable, mSampleInterval);
                synchronized (mStackMap) {
                    mStackMap.clear();
                }
                return;
            }
            if (it.contains("Finished")) {
                long useTime = currentTimeMillis - lastFrameTime;
                //记录时间
                if (useTime > 20) {
                    //todo 要判断哪里耗时操作导致的
                    Log.i(TAG, "ANR:" + it + ", useTime:" + useTime);
                    //大于100毫秒,则打印出来卡顿日志
                    if (useTime > 100) {
                        synchronized (mStackMap) {
                            Log.i(TAG, "mStackMap.size:" + mStackMap.size());
                            for (String key : mStackMap.keySet()) {
                                Log.i(TAG, "key:" + key + ",state:" + mStackMap.get(key));
                            }
                            mStackMap.clear();
                        }
                    }
                }
                threadHandler.removeCallbacks(mRunnable);
            }
        }
    };


    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            doSample();
            threadHandler
                    .postDelayed(mRunnable, mSampleInterval);
        }
    };

    protected void doSample() {
        StringBuilder stringBuilder = new StringBuilder();

        for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
            stringBuilder
                    .append(stackTraceElement.toString())
                    .append("\n");
        }
        synchronized (mStackMap) {
            mStackMap.put(mStackMap.size() + "", stringBuilder.toString());
        }
    }

}

Supongo que te gusta

Origin blog.csdn.net/AA5279AA/article/details/123187510
Recomendado
Clasificación