Threads, Handler, Looper e Message Queue: Parte 2
--
Continuando a conversa👨🏻💻
Começamos a falar desse tema na Parte 1 — Threads. Se você ainda não leu, recomendo conferir lá 😁👍🏻.
Handler
Um Handler
nos permite enviar e processar objetos Message
e Runnable
associados à uma fila de mensagens de uma Thread
.
Cada instância do Handler está associada a uma única thread e à fila de mensagens dessa thread. Quando você cria um novo Handler, ele é vinculado a thread que o criou. Daí em diante ele entregará Message
e Runnable
para essa fila de mensagens e os executará assim que saírem da fila de mensagens.
Existem dois usos principais para um Handler:
- Agendar mensagens e runnables para serem executados em algum momento no futuro;
- Enfileirar uma ação a ser executada em uma thread diferente da sua.
Ao enviar uma mensagem para um Handler, você pode permitir que ela seja processada assim que a fila de mensagens estiver pronta para isso ou pode também especificar um delay antes de ser processada ou tempo absoluto para que seja processada.
Relação Handler — Main Thead
Quando um processo é criado para o app, a main thread é dedicada a executar uma fila de mensagens que cuida do gerenciamento dos objetos top-level da aplicação (activities, broadcast receivers, services, …). Você pode criar suas próprias threads e se comunicar com a main thread do app por meio de um Handler. Isso é feito chamando os mesmos métodos post ou sendMessage, mas a partir da sua nova thread. O Runnable ou Message fornecido será então agendado na fila de mensagens do Handler e processado quando apropriado.
Looper
É uma classe usada para executar loop de mensagem para uma thread. As threads por padrão não tem um loop de mensagem associado a ela. Existem dois métodos fundamentais para utilizar um Looper em uma thread:
Looper.prepare()
: Inicializa a thread atual como um looper. Isso lhe dá a chance de criar Handler's que fazem referência a esse looper, antes de realmente iniciar o loop.Looper.loop()
: Executa a fila de mensagem para a thread atual. Para encerrar o loop, basta chamar oquit
.Looper.myLoop()
: Devolve o looper que foi criado e associado a thread atual.
Looper, loop, parece tudo muito confuso com esses nomes 🤯, mas existe uma explicação. O Looper é uma classe que cria um loop infinito (imagine um
while(true) {...}
ou umfor(;;)
), para processar mensagens em uma fila. Agora os nomes fazem todo sentido, não acha? 😅
A maior parte das interações que um Looper tem é com um Handler.
Exemplo: Thread — Handler — Looper
Aqui está um exemplo clássico de uma implementação de uma thread que usa um Looper (com prepare
e loop
) para criar um Handler
que se comunica com o Looper.
MessageQueue
É uma classe que contém uma lista de mensagens para serem entregues a um Looper. As mensagens não são adicionadas na MessageQueue. Isso acontece através de um Handler associado ao Looper.
Para tornar mais claro nosso entendimento a respeito de Handler, Looper e MessageQueue vamos analisar o diagrama a seguir:
- O Handler envia mensagens para a MessageQueue (
post
,sendMessage
,postDelayed
, …); - O Looper solicita mensagens para a MessageQueue;
- A mensagem recebida pelo Looper é enviada para o Handler processar (ele recebe essa mensagem em um
Handler.Callback
).
Handler's e mensagens em diferentes threads
Imagine que você quer criar uma thread para realizar algum tipo de processamento, alguma operação de I/O que não é permitida na main thread ou qualquer outra coisa que pode de alguma forma bloquear a execução da main thread. Porém, após o processamento, você gostaria de retornar para a main thread com o resultado.
Podemos criar uma thread com um Looper e um Handler para receber uma mensagem, processá-la e enviar o resultado ao finalizar o processamento.
No exemplo acima (ilustrativo apenas), criamos uma thread com um Looper e um Handler que recebe uma url
de uma imagem e devolve um path
de um arquivo após baixar a imagem. Podemos ver que o resultado é enviado ao mainHandler
para que seja recebido na main thread.
Aqui podemos ver uma das formas de criar um Handler que escuta mensagens na main thread. Podemos passá-lo para a nossa thread para receber os resultados do processamento em background da ImageFileThread
.
Vale ressaltar que nesse exemplo a thread não cancela ou é interrompida quando termina de processar uma mensagem. Em nenhum ponto chamamos
Looper.quit()
para encerrar o loop.
Os exemplos apresentados aqui são exclusivamente de propósito didático para compreender o funcionamento dessas ferramentas. Existem outras formas melhores de lidar com threading e assincronismo no Android (RxJava e Kotlin Coroutines, por exemplo).
Se você gostou deste post, não deixe de compartilhar com outros devs que você conhece! 🤗
Muito obrigado por ler até aqui!