Un breve análisis del mecanismo Android Looper Handler

Recientemente quise escribir una demostración del reproductor que usa Looper Handler. Leí mucha información pero no pude entenderla completamente, así que decidí echar un vistazo al código fuente relevante y registrar mi experiencia aquí, con la esperanza de ayudar. Gente necesitada.

Este artículo utilizará 猜想 + log验证el método para aprender Android Looper Handler. Se omitirán algunos códigos complejos, siempre que pueda comprender sus principios de diseño. Las opiniones en este artículo son todas mis humildes opiniones, si hay algún error, hágamelo saber.

Por favor agregue la descripción de la imagen.

1. Cola de mensajes del controlador de looper

Looper juega un papel en todo el mecanismo de procesamiento de mensajes.Mensaje en espera y distribución de mensajes.Función:

  • Mensaje en espera: Looper bloquea la ejecución del programa y espera los próximos mensajes;
  • Distribución de mensajes: después de que Looper recibe el mensaje, lo distribuye al procesador designado para procesarlo en el hilo actual.

Primero, veamos el problema del bloqueo. Cuando se inicia la aplicación, creará un Looper y llamará a la función de bucle para bloquear la función principal. Este Looper se llama hilo principal/proceso principal Looper. Para el código, consulte ActivityThread .java :

    public static void main(String[] args) {
    
    
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        Looper.prepareMainLooper();
        Looper.loop();
    }

El hilo principal Looper (o también llamado mainLooper) tiene la función de evitar que el programa finalice, esto dará lugar a otras preguntas ¿Por qué el programa puede seguir ejecutándose después de que lo bloquea?

En la función de mensaje en espera anterior, mencionamos que cuando el programa de bloqueo Looper se esté ejecutando, esperará el siguiente mensaje. ¿Qué mensaje llegará?

Escribí una demostración con solo un botón en la interfaz. Haga clic en el ícono para iniciar la aplicación y haga clic en el botón. Luego observaremos el registro:

2023-08-28 22:17:51.969 6768-6768/com.example.loopertest D/MainActivity: onCreate
2023-08-28 22:17:51.993 6768-6768/com.example.loopertest D/MainActivity: onStart
2023-08-28 22:17:51.993 6768-6768/com.example.loopertest D/MainActivity: onPostCreate
2023-08-28 22:17:51.994 6768-6768/com.example.loopertest D/MainActivity: onResume
2023-08-28 22:17:51.994 6768-6768/com.example.loopertest D/MainActivity: onPostResume
2023-08-28 22:17:54.424 6768-6768/com.example.loopertest D/MainActivity: onClick

Puede ver que el número de hilo del registro es el mismo que el número de proceso, lo que significa que todos los mensajes se procesan en el hilo principal (hilo de UI) / proceso principal, luego el Looper recibe el evento de inicio de la Actividad y hace clic. evento del botón. Por extensión, el proceso principal recibirá y manejará todos los eventos de la interfaz de usuario.

¿Cómo recibe Looper eventos y los procesa? Hay un bucle infinito en el método de bucle, loopOnce se ejecuta continuamente y aquí también se produce el bloqueo:

    public static void loop() {
    
    
        for (;;) {
    
    
            if (!loopOnce(me, ident, thresholdOverride)) {
    
    
                return;
            }
        }
    }

Verá un objeto en loopOnce MessageQueue, que es la cola de mensajes mantenida por Looper, que mantiene una lista vinculada internamente y también completa la espera de mensajes.

    private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
    
    
        Message msg = me.mQueue.next(); // might block
        if (msg == null) {
    
    
            // No message indicates that the message queue is quitting.
            return false;
        }
        ...
        try {
    
    
            msg.target.dispatchMessage(msg);
            if (observer != null) {
    
    
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } 
        ....
        return true;
	}

MessageQueue.nextEs un método de bloqueo. Cuando no hay ningún mensaje, se bloqueará y esperará, lo que puede evitar que el bucle for de nivel superior esté inactivo; cuando hay un mensaje, devuelve el mensaje y lo distribuye al controlador designado para su procesamiento.

Miremos el bucle nuevamente: ¿Cómo salir del bucle? En el código anterior podemos ver que cuando el mensaje devuelto es nulo, loopOnce devuelve falso y todo el ciclo finaliza.

El código MessageQueue.next es el siguiente:

    Message next() {
    
    
		......
        for (;;) {
    
    
			......
            nativePollOnce(ptr, nextPollTimeoutMillis);
            ......
            if (mQuitting) {
    
    
                dispose();
                return null;
            }   
        }   
    }

El código aquí es relativamente largo y el contenido principal es:

  1. Llame a NativePollOnce para bloquear el mensaje de encuesta en la capa nativa;
  2. Ordene los mensajes sondeados de acuerdo con el tiempo de ejecución especificado. Si no se especifica ningún tiempo, los mensajes se ordenarán en orden de finalización;
  3. Si es necesario retrasar el primer mensaje de la lista vinculada, continúe llamando a NativePollOnce y establezca un tiempo de espera;
  4. Devuelve el mensaje que debe procesarse a loopOnce;
  5. Si se llama al método quit, se devuelve nulo, lo que finaliza el ciclo.

En el código anterior, vemos que la forma de obtener mensajes es llamar a NativePollOnce para esperar. Los mensajes nativos aquí pueden ser enviados a mainLooper por el sistema Android, como eventos de clic táctil, etc. Además, también podemos enviar mensajes activamente a mainLooper, lo que requiere el uso de Handler.

Llamamos al método sendMessage del controlador, que eventualmente se ejecutará en 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);
    }

Llamará MessageQueue.enqueueMessagepara agregar el mensaje a la lista vinculada de mensajes. La pregunta es, ¿de dónde viene el MessageQueue aquí?

Esto debe rastrearse hasta el constructor del Handler, y se debe pasar un Looper como parámetro, y MessageQueue se obtiene del Looper.

Esto significa que cada Handler solo puede manejar una transacción de Looper. ¿Por qué sólo se puede procesar una transacción de Looper? Tengo entendido que Looper recopila todos los mensajes o transacciones y luego los reenvía uno por uno para su ejecución. Aunque los mensajes se envían de forma asincrónica, el Handler ejecuta la tarea de forma sincrónica, por lo que no hay problema de sincronización de subprocesos.

    boolean enqueueMessage(Message msg, long when) {
    
    
        if (msg.target == null) {
    
    
            throw new IllegalArgumentException("Message must have a target.");
        }
        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;
            }
            if (needWake) {
    
    
                nativeWake(mPtr);
            }
        }
        return true;
    }

Después de ingresar MessageQueue.enqueueMessage, el mensaje se agregará a la cola. Si la cola de mensajes actual está vacía, se llamará a NativeWake para interrumpir la ejecución de NativePollOnce, procesando así inmediatamente el mensaje que publicamos activamente.

Cuando Looper distribuye mensajes, ¿quién debe encargarse de manejarlo? La respuesta es qué controlador envía el mensaje y qué controlador procesará el mensaje. Puede consultar el método enqueueMessage para esto.

Además de utilizar el controlador para publicar mensajes, el mensaje en sí también tiene un sendToTargetmétodo que puede enviarse al controlador especificado, y luego el controlador se agregará a la cola MessageQueue. Si está interesado, puede leer el código relacionado.

Además de usar Handler sendMessage, a menudo vemos el uso de Handler post Runnable. ¿Qué es ejecutable?

El mensaje enviado por sendMessage a menudo se configura con qué información, y luego el controlador realizará el procesamiento correspondiente en función de qué. El ejemplo de código es el siguiente:

        Handler handler = new Handler(getMainLooper()) {
    
    
            @Override
            public void handleMessage (Message msg) {
    
    
                switch (msg.what) {
    
    
                    case 1:
                        break;
                    default:
                        break;
                }
            }
        };
        Message msg = Message.obtain();
        msg.what = 1;
        handler.sendMessage(msg);

A veces no queremos que el Handler identifique y procese los mensajes que enviamos, sino que solo queremos completar una tarea, en este caso podemos publicar Runnable. Runnable encapsulará un mensaje en forma de devolución de llamada. Durante el procesamiento de distribución, la transacción escrita en Runnable se ejecutará directamente y no es necesario ingresar el método handleMessage.

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

También podemos volcar el mensaje en el Looper para ver el contenido en MessageQueue y el estado actual del Looper. El siguiente es un volcado en el método onClick del botón. El registro es el siguiente:

2023-08-28 23:50:15.953 7495-7495/com.example.loopertest D/MainActivity: onClick
2023-08-28 23:50:15.953 7495-7495/com.example.loopertest D/MainActivity: Looper (main, tid 2) {
    
    f4105a7}
2023-08-28 23:50:15.954 7495-7495/com.example.loopertest D/MainActivity:   Message 0: {
    
     when=-2ms callback=android.view.View$UnsetPressedState target=android.view.ViewRootImpl$ViewRootHandler }
2023-08-28 23:50:15.954 7495-7495/com.example.loopertest D/MainActivity:   (Total messages: 1, polling=false, quitting=false)

En este punto, los principios básicos de Looper Handler MessageQueue han terminado, a continuación, comprendamos cómo se utilizan realmente.

2 、 Cómo utilizar

2.1 、 Hilo

Antes de saber cómo utilizarlo, hay que tener en cuenta dos puntos:

  1. El procesamiento del contenido de la interfaz de usuario solo se puede colocar en el hilo principal;
  2. Un hilo sólo puede tener un Looper;

El segundo punto es que un hilo solo puede tener un Looper porque cada hilo solo puede bloquearse en un lugar. Si un hilo tiene dos Loopers, entonces el bucle de un Looper será bloqueado por el bucle del otro Looper.

Según el contenido anterior, sabemos que cuando se inicia la aplicación, creará automáticamente un MainLooper para procesar mensajes relacionados con la interfaz de usuario. Sin embargo, en la escritura real de la aplicación, habrá varias tareas. Si todas las tareas se colocan en el hilo de la interfaz de usuario, Si se ejecuta durante la ejecución, puede afectar el procesamiento de eventos de la interfaz de usuario y provocar ANR y otras situaciones.

Por ejemplo, agrego el siguiente código a onCreate:

        new Handler(getMainLooper()).post(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 5; i++) {
    
    
                    try {
    
    
                        Log.d(LOG_TAG, "i = " + i +" thread id = " + Process.myTid());
                        sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        });

Como puede ver, al iniciar, debe esperar a que Runnable termine de ejecutarse antes de renderizar la interfaz de usuario, lo que obviamente no es apropiado. Para resolver el problema de las transacciones generales que ocupan el hilo de la interfaz de usuario, las transacciones no relacionadas con la interfaz de usuario o algunas transacciones que requieren mucho tiempo a menudo se procesan en un nuevo hilo.

La forma más sencilla de utilizar subprocesos con Looper Handler es la siguiente:

		// 问题示例
		private Handler handler;
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                Looper.prepare();
                handler = new Handler(Looper.myLooper()) {
    
    
                    @Override
                    public void handleMessage (Message msg) {
    
    
                        switch (msg.what) {
    
    
                            case 1:
                                Log.d(LOG_TAG, "Process Message 1");
                                break;
                            default:
                                break;
                        }
                    }
                };
                Looper.loop();
            }
        }).start();
        Message msg = Message.obtain();
        msg.what = 1;
        handler.sendMessage(msg);

Para usar Looper Handler en un hilo secundario, tenemos 3 cosas que hacer:

  1. Crear e iniciar hilos;
  2. Cree un Looper en el hilo y llame a Looper.loop para bloquear el hilo;
  3. Cree un controlador y vincúlelo al Looper en el hilo;

¿Hay algún problema con el código anterior? Ejecutemos la aplicación y echemos un vistazo, ¡ah! Habrá un error de puntero nulo:

08-28 16:05:44.815  8436  8436 E AndroidRuntime: Process: com.example.loopertest, PID: 8436
08-28 16:05:44.815  8436  8436 E AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{
    
    com.example.loopertest/com.example.loopertest.MainActivity}: java.lang.NullPointerExceptio
n: Attempt to invoke virtual method 'boolean android.os.Handler.sendMessage(android.os.Message)' on a null object reference

¡Analiza con calma! Esto se debe a que el inicio del subproceso se ejecutará en paralelo con sendMessage. Cuando se ejecuta sendMessage, es posible que el controlador aún no se haya creado, por lo que se producirá un error de puntero nulo. La solución es agregar un retraso antes de enviarMessage para garantizar que se haya creado el controlador:

		// 解决办法
        Message msg = Message.obtain();
        msg.what = 1;
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        handler.sendMessage(msg);

En este momento, alguien puede preguntar, ¿por qué no creo Looper fuera del hilo primero y luego llamo a Looper.loop dentro del hilo? Buena pregunta. Recuerda lo que dijimos antes de que un hilo solo puede tener un Looper. El externo es el hilo principal. Si se crea externamente, el hilo principal tendrá dos Loopers. Veamos la declaración de Looper:

public final class Looper {
    
    
	static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
	private static Looper sMainLooper;  // guarded by Looper.class
    public static void prepare() {
    
    
        prepare(true);
    }

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

sMainLooperEs una variable estática, lo que significa que solo puede haber un MainLooper en nuestro proceso, que es lo que llamamos Looper del hilo principal; además, Java también proporciona una variable estática local del hilo para garantizar que las variables estáticas de sThreadLocal cada los hilos son diferentes...

El método prepare se utiliza para crear un Looper en un hilo. Si se llama a prepare dos veces en un hilo, se lanzará una excepción. Esto también confirma lo que dijimos anteriormente de que un hilo solo puede tener un Looper.

En este momento, alguien puede volver a preguntar, ¿por qué no creo una instancia de Handler afuera y luego vinculo el Looper del Thread al Handler? No está mal, intentémoslo:

    class MyThread extends Thread {
    
    
        @Override
        public void run() {
    
    
            Looper.prepare();
            Looper.loop();
        }

        Looper getLooper() {
    
    
            return Looper.myLooper();
        }
    }

	private MyThread myThread;
    myThread = new MyThread();
    myThread.start();
    handler = new Handler(myThread.getLooper()) {
    
    
        @Override
        public void handleMessage (Message msg) {
    
    
            switch (msg.what) {
    
    
                case 1:
                    Log.d(LOG_TAG, "Process Message 1");
                    break;
                default:
                    break;
            }
        }
    };
    Message msg = Message.obtain();
    msg.what = 1;
    handler.sendMessage(msg);

Los resultados reales de la ejecución son los siguientes, ¡no hay problema! Esto se utilizará a partir de ahora.

2023-08-29 21:27:16.583 9284-9284/com.example.loopertest D/MainActivity: Process Message 1

2.2、HandlerThread

¿Quieres aplaudir nuestro ingenio? ¡Esperar! Android parece haber implementado el método que queremos, ¡aquí está HandlerThread! HandlerThread tiene exactamente la misma idea que la nuestra, por lo que el uso es exactamente el mismo. A continuación se muestran ejemplos sin mayor explicación.

        handlerThread = new HandlerThread("Test HandlerThread");
        handlerThread.start();
        Message msg = Message.obtain();
        msg.what = 1;
        new Handler(handlerThread.getLooper()) {
    
    
            @Override
            public void handleMessage (Message msg) {
    
    
                switch (msg.what) {
    
    
                    case 1:
                        Log.d(LOG_TAG, "Process Message 1");
                        break;
                    default:
                        break;
                }
            }
        }.sendMessage(msg);
2023-08-29 00:15:51.853 8743-8743/com.example.loopertest D/MainActivity: onCreate
2023-08-29 00:15:51.871 8743-8858/com.example.loopertest D/MainActivity: Process Message 1
2023-08-29 00:15:51.871 8743-8743/com.example.loopertest D/MainActivity: onStart
2023-08-29 00:15:51.872 8743-8743/com.example.loopertest D/MainActivity: onPostCreate
2023-08-29 00:15:51.872 8743-8743/com.example.loopertest D/MainActivity: onResume
2023-08-29 00:15:51.872 8743-8743/com.example.loopertest D/MainActivity: onPostResume

2.3 Enviarse mensajes entre hilos

A menudo podemos escuchar que el hilo secundario envía mensajes al hilo principal, el hilo principal envía mensajes al hilo secundario y el hilo secundario envía mensajes al hilo secundario. ¿Estás un poco mareado cuando ves tantos? situaciones?

De hecho, los principios de estos tres tipos de mensajería mutua son los mismos. Recuerde lo que dijimos anteriormente, el Handler debe estar vinculado a un Looper cuando se crea:

public Handler(@NonNull Looper looper) {
    
    
	this(looper, null, false);
}

Después de vincular el Looper, el Handler solo puede procesar los mensajes distribuidos por el Looper vinculado, entonces, ¿de dónde provienen los mensajes en el Looper vinculado? Además de los mensajes enviados de forma nativa, ¿podemos llamar al Handler vinculado a Looper para enviar mensajes?

Es decir, para permitir que el mensaje se ejecute en el hilo Looper especificado, simplemente llame al método post/sendMessage del Handler vinculado al Looper del hilo.

¡Es así de simple!

2.4 Cómo detener Looper

Aquí quiero hablar sobre el problema de pérdida de memoria más común en Internet. Primero, dé un ejemplo (no sé si lo entendí correctamente):

Agregamos el siguiente código al método onCreate y cerramos la aplicación inmediatamente después de abrirla.

        new Handler(getMainLooper()).postDelayed(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 5; i++) {
    
    
                    try {
    
    
                        Log.d(LOG_TAG, "i = " + i +" thread id = " + Process.myTid());
                        sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        }, 5000);

Puedes ver los siguientes fenómenos:

2023-08-29 21:51:30.568 9599-9599/com.example.loopertest D/MainActivity: onCreate
2023-08-29 21:51:30.586 9599-9599/com.example.loopertest D/MainActivity: onStart
2023-08-29 21:51:30.587 9599-9599/com.example.loopertest D/MainActivity: onPostCreate
2023-08-29 21:51:30.587 9599-9599/com.example.loopertest D/MainActivity: onResume
2023-08-29 21:51:30.587 9599-9599/com.example.loopertest D/MainActivity: onPostResume
2023-08-29 21:51:31.783 9599-9599/com.example.loopertest D/MainActivity: onKeyDown keyCode = 4
2023-08-29 21:51:31.873 9599-9599/com.example.loopertest D/MainActivity: onPause
2023-08-29 21:51:32.399 9599-9599/com.example.loopertest D/MainActivity: onStop
2023-08-29 21:51:32.399 9599-9599/com.example.loopertest D/MainActivity: onDestroy
2023-08-29 21:51:35.624 9599-9599/com.example.loopertest D/MainActivity: i = 0 thread id = 9599
2023-08-29 21:51:36.635 9599-9599/com.example.loopertest D/MainActivity: i = 1 thread id = 9599
2023-08-29 21:51:37.677 9599-9599/com.example.loopertest D/MainActivity: i = 2 thread id = 9599
2023-08-29 21:51:38.701 9599-9599/com.example.loopertest D/MainActivity: i = 3 thread id = 9599
2023-08-29 21:51:39.744 9599-9599/com.example.loopertest D/MainActivity: i = 4 thread id = 9599

¡Ey! Obviamente se ha llamado a onDestroy, ¿por qué aún se puede imprimir el contenido del bucle? Me pregunto si esta es la pérdida de memoria mencionada en otras publicaciones del blog. Al salir de la actividad, los recursos de la actividad todavía están ocupados, lo que hace que los recursos no se liberen normalmente.

Algunas soluciones en Internet son declarar Handler como una clase estática, pero no creo que sea una buena solución aquí.

Al salir de un programa o actividad, el hilo donde se encuentra el Looper se está ejecutando y normalmente necesitamos detener el hilo. Dado que Looper.loop está bloqueando, debemos llamar Looper.quito Looper.quitSafelysalir del bucle infinito.

Quiero hacer una pregunta aquí: Después de llamar a quit / quitSafely, ¿Looper realmente se detiene?

La respuesta es no, Looper no necesariamente se detiene inmediatamente, necesitaSólo se puede detener después de ejecutar la tarea actual! Si la tarea actual requiere mucho tiempo, en realidad no se detendrá hasta que termine de ejecutar el Looper.

¿Qué tenemos que hacer?

  1. Agregue un mecanismo de interrupción al ejecutar trabajos que requieren mucho tiempo en Looper;
  2. Looper.quit se llama cuando finaliza la actividad;
  3. Llame a Thread.join para bloquear y espere a que finalice el hilo;

Creo que los recursos se pueden liberar normalmente y terminar de esta manera, no sé si mi entendimiento es correcto. A continuación, utilice HandlerThread para dar un ejemplo:

Agregue el siguiente código en onCreate y onDestroy:

	protected void onCreate(Bundle savedInstanceState) {
    
    
        handlerThread = new HandlerThread("Test HandlerThread quit");
        handlerThread.start();
        Message msg = Message.obtain();
        msg.what = 1;
        new Handler(handlerThread.getLooper()).post(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                int i = 0;
                while (i < 10) {
    
    
                    try {
    
    
                        Log.d(LOG_TAG, "i = " + i +" thread id = " + Process.myTid());
                        sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    i++;
                }
            }
        });
    }

    protected void onStop() {
    
    
        Log.d(LOG_TAG, "onStop");
        super.onStop();
        handlerThread.quitSafely();
    }

Salga de la aplicación inmediatamente después de iniciarla. Puede ver que hemos llamado a quitSafely, pero el hilo aún no se detiene de inmediato y los recursos no se liberan normalmente:

2023-08-29 22:43:24.101 10559-10559/com.example.loopertest D/MainActivity: onCreate
2023-08-29 22:43:24.203 10559-10586/com.example.loopertest D/MainActivity: i = 0 thread id = 10586
2023-08-29 22:43:24.207 10559-10559/com.example.loopertest D/MainActivity: onStart
2023-08-29 22:43:24.210 10559-10559/com.example.loopertest D/MainActivity: onPostCreate
2023-08-29 22:43:24.211 10559-10559/com.example.loopertest D/MainActivity: onResume
2023-08-29 22:43:24.212 10559-10559/com.example.loopertest D/MainActivity: onPostResume
2023-08-29 22:43:25.205 10559-10586/com.example.loopertest D/MainActivity: i = 1 thread id = 10586
2023-08-29 22:43:26.004 10559-10559/com.example.loopertest D/MainActivity: onKeyDown keyCode = 4
2023-08-29 22:43:26.207 10559-10586/com.example.loopertest D/MainActivity: i = 2 thread id = 10586
2023-08-29 22:43:26.231 10559-10559/com.example.loopertest D/MainActivity: onPause
2023-08-29 22:43:26.765 10559-10559/com.example.loopertest D/MainActivity: onStop
2023-08-29 22:43:26.766 10559-10559/com.example.loopertest D/MainActivity: onDestroy
2023-08-29 22:43:27.208 10559-10586/com.example.loopertest D/MainActivity: i = 3 thread id = 10586
2023-08-29 22:43:28.242 10559-10586/com.example.loopertest D/MainActivity: i = 4 thread id = 10586
2023-08-29 22:43:29.258 10559-10586/com.example.loopertest D/MainActivity: i = 5 thread id = 10586
2023-08-29 22:43:30.275 10559-10586/com.example.loopertest D/MainActivity: i = 6 thread id = 10586
2023-08-29 22:43:31.293 10559-10586/com.example.loopertest D/MainActivity: i = 7 thread id = 10586
2023-08-29 22:43:32.308 10559-10586/com.example.loopertest D/MainActivity: i = 8 thread id = 10586
2023-08-29 22:43:33.348 10559-10586/com.example.loopertest D/MainActivity: i = 9 thread id = 10586

Agregamos unirse después de quitSafely. Puede ver que al salir del programa, onStop se bloquea en la posición de unión y espera a que finalice el hilo:

2023-08-29 22:43:56.616 10618-10618/com.example.loopertest D/MainActivity: onCreate
2023-08-29 22:43:56.745 10618-10644/com.example.loopertest D/MainActivity: i = 0 thread id = 10644
2023-08-29 22:43:56.748 10618-10618/com.example.loopertest D/MainActivity: onStart
2023-08-29 22:43:56.749 10618-10618/com.example.loopertest D/MainActivity: onPostCreate
2023-08-29 22:43:56.750 10618-10618/com.example.loopertest D/MainActivity: onResume
2023-08-29 22:43:56.750 10618-10618/com.example.loopertest D/MainActivity: onPostResume
2023-08-29 22:43:57.746 10618-10644/com.example.loopertest D/MainActivity: i = 1 thread id = 10644
2023-08-29 22:43:58.748 10618-10644/com.example.loopertest D/MainActivity: i = 2 thread id = 10644
2023-08-29 22:43:59.240 10618-10618/com.example.loopertest D/MainActivity: onKeyDown keyCode = 4
2023-08-29 22:43:59.354 10618-10618/com.example.loopertest D/MainActivity: onPause
2023-08-29 22:43:59.750 10618-10644/com.example.loopertest D/MainActivity: i = 3 thread id = 10644
2023-08-29 22:43:59.863 10618-10618/com.example.loopertest D/MainActivity: onStop
2023-08-29 22:44:00.784 10618-10644/com.example.loopertest D/MainActivity: i = 4 thread id = 10644
2023-08-29 22:44:01.797 10618-10644/com.example.loopertest D/MainActivity: i = 5 thread id = 10644
2023-08-29 22:44:02.840 10618-10644/com.example.loopertest D/MainActivity: i = 6 thread id = 10644
2023-08-29 22:44:03.881 10618-10644/com.example.loopertest D/MainActivity: i = 7 thread id = 10644
2023-08-29 22:44:04.922 10618-10644/com.example.loopertest D/MainActivity: i = 8 thread id = 10644
2023-08-29 22:44:05.926 10618-10644/com.example.loopertest D/MainActivity: i = 9 thread id = 10644
2023-08-29 22:44:06.948 10618-10618/com.example.loopertest D/MainActivity: onDestroy

Agreguemos otra condición para la salida del hilo:

        new Handler(handlerThread.getLooper()).post(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                int i = 0;
                while (i < 10) {
    
    
                    if (isQuit)
                        break;
                    try {
    
    
                        Log.d(LOG_TAG, "i = " + i +" thread id = " + Process.myTid());
                        sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    i++;
                }
            }
        });

Como puede ver, cuando hacemos clic en el botón Volver para salir de la aplicación, el programa puede salir normalmente y el subproceso ya no imprime contenido, por lo que no habrá pérdidas de memoria.

2023-08-29 22:48:03.391 10748-10748/com.example.loopertest D/MainActivity: onCreate
2023-08-29 22:48:03.525 10748-10773/com.example.loopertest D/MainActivity: i = 0 thread id = 10773
2023-08-29 22:48:03.528 10748-10748/com.example.loopertest D/MainActivity: onStart
2023-08-29 22:48:03.529 10748-10748/com.example.loopertest D/MainActivity: onPostCreate
2023-08-29 22:48:03.530 10748-10748/com.example.loopertest D/MainActivity: onResume
2023-08-29 22:48:03.530 10748-10748/com.example.loopertest D/MainActivity: onPostResume
2023-08-29 22:48:04.525 10748-10773/com.example.loopertest D/MainActivity: i = 1 thread id = 10773
2023-08-29 22:48:05.527 10748-10773/com.example.loopertest D/MainActivity: i = 2 thread id = 10773
2023-08-29 22:48:06.528 10748-10773/com.example.loopertest D/MainActivity: i = 3 thread id = 10773
2023-08-29 22:48:06.733 10748-10748/com.example.loopertest D/MainActivity: onKeyDown keyCode = 4
2023-08-29 22:48:06.854 10748-10748/com.example.loopertest D/MainActivity: onPause
2023-08-29 22:48:07.399 10748-10748/com.example.loopertest D/MainActivity: onStop
2023-08-29 22:48:07.531 10748-10748/com.example.loopertest D/MainActivity: onDestroy

Bien, eso es todo por el análisis del mecanismo Android Looper Handler. Si encuentra útil este artículo, no dude en darle me gusta y seguirlo. ¡Adiós!

Supongo que te gusta

Origin blog.csdn.net/qq_41828351/article/details/132529181
Recomendado
Clasificación