Uma breve análise do mecanismo Android Looper Handler

Recentemente eu queria escrever uma demonstração de jogador, que usa o Looper Handler. Li muitas informações, mas não consegui entendê-las completamente, então decidi dar uma olhada no código-fonte relevante e registrar minha experiência aqui, na esperança de ajudar pessoas em necessidade.

Este artigo usará 猜想 + log验证o método para aprender o Android Looper Handler. Alguns códigos complexos serão ignorados, desde que você possa entender seus princípios de design. As opiniões neste artigo são todas minhas humildes opiniões. Se houver algum erro, por favor me avise.

Adicione a descrição da imagem

1. MessageQueue do manipulador de looper

Looper desempenha um papel em todo o mecanismo de processamento de mensagensMensagem em espera e distribuição de mensagensFunção:

  • Mensagem em espera: Looper bloqueia a execução do programa e aguarda as próximas mensagens;
  • Distribuição de mensagens: após o Looper receber a mensagem, ele a distribui ao processador designado para processamento no thread atual.

Primeiro, vamos examinar o problema de bloqueio. Quando o aplicativo é iniciado, ele cria um Looper e chama a função de loop para bloquear a função principal. Esse Looper é chamado de Looper de thread/processo principal. Para obter o código, consulte ActivityThread .java :

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

O thread principal Looper (ou também chamado de mainLooper) tem a função de impedir que o programa termine. Isso levará a outras questões: Por que o programa pode continuar em execução após ser bloqueado?

Na função de mensagem em espera acima, mencionamos que quando o programa de bloqueio do Looper estiver em execução, ele aguardará a próxima mensagem. Que mensagem virá?

Escrevi uma demonstração com apenas um botão na interface. Clique no ícone para iniciar o aplicativo e clique no botão. Em seguida observaremos o log:

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

Você pode ver que o número do thread do log é igual ao número do processo, o que significa que todas as mensagens são processadas no thread principal (thread UI)/processo principal. Em seguida, o Looper recebe o evento de início da Activity e o clique evento do botão. Por extensão, o processo principal receberá e tratará todos os eventos da UI.

Como o Looper recebe eventos e os processa? Há um loop infinito no método loop, loopOnce é executado continuamente e o bloqueio também ocorre aqui:

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

Você verá um objeto em loopOnce MessageQueue, que é a fila de mensagens mantida pelo Looper. Ele mantém uma lista vinculada internamente e a espera de mensagens também é concluída por ele.

    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.nextÉ um método de bloqueio. Quando não há mensagem, ele irá bloquear e esperar, o que pode evitar que o loop for de nível superior fique ocioso; quando há uma mensagem, ele retorna Mensagem e a distribui ao manipulador designado para processamento.

Vejamos o loop novamente: Como sair do loop? No código acima, podemos ver que quando a mensagem retornada é nula, loopOnce retorna falso e todo o loop termina.

O código MessageQueue.next é o seguinte:

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

O código aqui é relativamente longo, o conteúdo principal é:

  1. Chame nativePollOnce para bloquear a mensagem de enquete na camada nativa;
  2. Classifique as mensagens pesquisadas de acordo com o tempo de execução especificado. Se nenhum horário for especificado, as mensagens serão classificadas na ordem de término;
  3. Se a primeira mensagem na lista vinculada precisar ser atrasada, continue chamando nativePollOnce e defina um tempo limite;
  4. Retorne a mensagem que precisa ser processada para loopOnce;
  5. Se o método quit for chamado, null será retornado, encerrando o loop.

No código acima, vemos que a forma de obter mensagens é chamar nativePollOnce para esperar.As mensagens nativas aqui podem ser enviadas para mainLooper pelo sistema Android, como eventos de clique de toque e assim por diante. Além disso, também podemos enviar mensagens ativamente para mainLooper, o que requer o uso de Handler.

Chamamos o método sendMessage do Handler, que eventualmente será executado em 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);
    }

Ligará MessageQueue.enqueueMessagepara adicionar a mensagem à lista vinculada de mensagens. A questão é: de onde vem o MessageQueue aqui?

Isso precisa ser rastreado até o construtor do Handler, e um Looper precisa ser passado como parâmetro.O MessageQueue é obtido do Looper.

Isso significa que cada Handler só pode lidar com uma transação do Looper. Por que apenas uma transação do Looper pode ser processada? Meu entendimento é o seguinte: o Looper coleta todas as mensagens ou transações e as encaminha uma por uma para execução. Embora as mensagens sejam enviadas de forma assíncrona, o Handler executa a tarefa de forma síncrona, portanto, não há problema de sincronização de thread.

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

Após inserir MessageQueue.enqueueMessage, a mensagem será adicionada à fila. Se a fila de mensagens atual estiver vazia, nativeWake será chamado para interromper a execução de nativePollOnce, processando imediatamente a mensagem que postamos ativamente.

Quando o Looper distribui mensagens, quem deve ser encarregado de lidar com isso? A resposta é qual Handler envia a mensagem, a mensagem será processada por qual Handler. Você pode verificar o método enqueueMessage para isso.

Além de usar o Handler para postar mensagens, o próprio Message também possui um sendToTargetmétodo que pode se enviar para o Handler especificado e, em seguida, o Handler será adicionado à fila MessageQueue. Se estiver interessado, você pode ler o código relevante.

Além de usar o Handler sendMessage, frequentemente vemos o uso do Handler post Runnable. O que é executável?

A mensagem enviada por sendMessage geralmente é definida com quais informações e, em seguida, o manipulador executará o processamento correspondente com base em quais. O exemplo de código é o seguinte:

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

Às vezes não queremos que o Handler identifique e processe as mensagens que enviamos, mas apenas queremos completar uma tarefa. Neste caso, podemos postar Runnable. O Runnable encapsulará uma mensagem na forma de um retorno de chamada. Durante o processamento da distribuição, a transação escrita no Runnable será executada diretamente e não há necessidade de inserir o 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);
        }
    }

Também podemos despejar a mensagem no Looper para ver o conteúdo do MessageQueue e o status atual do Looper. A seguir está um dump no método onClick do botão. O log é o seguinte:

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)

Neste ponto, os princípios básicos do Looper Handler MessageQueue estão concluídos. A seguir, vamos entender como eles são realmente usados.

2、Como usar

2.1、Tópico

Antes de saber como utilizá-lo, há dois pontos a serem observados:

  1. O processamento do conteúdo da UI só pode ser colocado no thread principal;
  2. Um thread só pode ter um Looper;

O segundo ponto é que um thread só pode ter um Looper porque cada thread só pode bloquear em um lugar. Se um thread tiver dois Loopers, então o loop de um Looper será bloqueado pelo loop do outro Looper.

De acordo com o conteúdo anterior, sabemos que quando o aplicativo for iniciado, ele criará automaticamente um MainLooper para processar mensagens relacionadas à IU. No entanto, na escrita real do aplicativo, haverá várias tarefas. Se todas as tarefas forem colocadas no thread da IU, Se for executado durante a execução, poderá afetar o processamento de eventos de UI e causar ANR e outras situações.

Por exemplo, adiciono o seguinte código ao 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 você pode ver, ao inicializar, você precisa esperar que o Runnable termine de ser executado antes de renderizar a UI, o que obviamente não é apropriado. Para resolver o problema de transações gerais que ocupam o thread da UI, as transações não relacionadas à UI ou algumas transações demoradas são frequentemente processadas em um novo thread.

A maneira mais simples de usar threads com Looper Handler é a seguinte:

		// 问题示例
		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 o Looper Handler em um thread filho, temos 3 coisas a fazer:

  1. Criar e iniciar tópicos;
  2. Crie um Looper no thread e chame Looper.loop para bloquear o thread;
  3. Crie um Handler e vincule-o ao Looper no thread;

Há algo de errado com o código acima? Vamos rodar o aplicativo e dar uma olhada, ah! Haverá um erro de ponteiro 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

Analise com calma! Isso ocorre porque a inicialização do thread será executada em paralelo com sendMessage.Quando sendMessage é executado, o manipulador pode ainda não ter sido criado, portanto, ocorrerá um erro de ponteiro nulo. A solução é adicionar um atraso antes de sendMessage para garantir que o manipulador foi criado:

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

Neste momento, alguém pode perguntar: por que não crio Looper fora do thread primeiro e depois chamo Looper.loop dentro do thread? Boa pergunta. Lembre-se do que dissemos antes que um thread só pode ter um Looper. O externo é o thread principal. Se for criado externamente, o thread principal terá dois Loopers. Vejamos a declaração 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));
    }

sMainLooperÉ uma variável estática, o que significa que só pode haver um MainLooper em nosso processo, que é o que chamamos de Looper do thread principal; além disso, Java também fornece uma variável estática local do thread para garantir que as variáveis ​​estáticas de sThreadLocal cada os fios são diferentes. .

O método prepare é usado para criar um Looper em um thread. Se prepare for chamado duas vezes em um thread, uma exceção será lançada. Isso também confirma o que dissemos acima de que um thread só pode ter um Looper.

Neste momento, alguém pode perguntar novamente, por que não crio uma instância do Handler externamente e depois vinculo o Looper do Thread ao Handler? Nada mal, vamos tentar:

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

Os resultados reais da execução são os seguintes, sem problemas! Isso será usado a partir de agora

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

2.2、HandlerThread

Você quer aplaudir nossa inteligência? Aguentar! O Android parece ter implementado o método que queríamos, é isso HandlerThread! HandlerThread tem exatamente a mesma ideia que a nossa, então o uso é exatamente o mesmo! Aqui estão exemplos sem maiores explicações.

        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. Enviem mensagens entre si entre tópicos

Muitas vezes podemos ouvir que o subthread envia mensagens para o thread principal, o thread principal envia mensagens para o subthread e o subthread envia mensagens para o subthread. Você fica um pouco tonto quando vê tantos situações?

Na verdade, os princípios desses três tipos de mensagens mútuas são os mesmos. Lembre-se do que dissemos acima, o Handler deve estar vinculado a um Looper quando for criado:

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

Depois de vincular o Looper, o Handler só pode processar as mensagens distribuídas pelo Looper vinculado. Então, de onde vêm as mensagens no Looper vinculado? Além das mensagens enviadas pelo nativo, podemos chamar o Handler vinculado ao Looper para enviar mensagens?

Ou seja, para permitir que a mensagem seja executada no thread Looper especificado, basta chamar o método post/sendMessage do Handler vinculado ao Looper do thread.

É simples assim!

2.4. Como parar o Looper

Quero falar aqui sobre o problema de vazamento de memória mais comum na Internet. Primeiro, dê um exemplo (não sei se entendi direito):

Adicionamos o seguinte código ao método onCreate e fechamos o aplicativo imediatamente após abri-lo

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

Você pode ver os seguintes 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

Ei! Obviamente onDestroy foi chamado, por que o conteúdo do loop ainda pode ser impresso? Gostaria de saber se esse é o vazamento de memória mencionado em outros posts do blog. Ao sair da atividade, os recursos da atividade ainda estão ocupados, fazendo com que os recursos não sejam liberados normalmente?

Algumas soluções na Internet são declarar Handler como uma classe estática. Não acho que esta seja uma boa solução aqui.

Ao sair de um programa ou atividade, o thread onde o Looper está localizado está em execução e normalmente precisamos interrompê-lo. Como Looper.loop está bloqueando, precisamos chamar Looper.quitou Looper.quitSafelysair do loop infinito.

Quero fazer uma pergunta aqui: depois de chamar quit / quitSafely, o Looper realmente para?

A resposta é não, o Looper não para necessariamente imediatamente, ele precisaSó pode ser interrompido após executar a tarefa atual! Se a tarefa atual for demorada, ela não irá realmente parar até terminar de executar o Looper.

O que nós temos que fazer?

  1. Adicione um mecanismo de interrupção ao executar trabalhos demorados no Looper;
  2. Looper.quit é chamado quando a atividade termina;
  3. Chame Thread.join para bloquear e aguarde o término do thread;

Acho que os recursos podem ser liberados normalmente e acabar assim, não sei se meu entendimento está correto. A seguir, use HandlerThread para dar um exemplo:

Adicione o seguinte código em onCreate e 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();
    }

Saia do aplicativo imediatamente após iniciá-lo. Você pode ver que chamamos quitSafely, mas o thread ainda não para imediatamente e os recursos não são liberados 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

Adicionamos join após quitSafely. Você pode ver que ao sair do programa, onStop bloqueia na posição de join e espera o thread terminar:

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

Vamos adicionar outra condição para saída do thread:

        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 você pode ver, quando clicamos no botão retornar para sair do aplicativo, o programa pode sair normalmente e o subthread não imprime mais o conteúdo, portanto não haverá vazamentos de memória.

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

Ok, é isso para a análise do mecanismo Android Looper Handler. Se você achar este artigo útil, não hesite em curtir e segui-lo. Adeus!

Acho que você gosta

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