Respostas:
A Looper
é um loop de tratamento de mensagens: ele lê e processa itens de a MessageQueue
. A Looper
classe é geralmente usada em conjunto com a HandlerThread
(uma subclasse de Thread
).
A Handler
é uma classe de utilitário que facilita a interação com um - Looper
principalmente postando mensagens e Runnable
objetos no thread MessageQueue
. Quando um Handler
é criado, ele é vinculado a um específico Looper
(e thread associado e fila de mensagens).
No uso típico, você cria e inicia um e HandlerThread
, em seguida, cria um Handler
objeto (ou objetos) pelo qual outros threads podem interagir com a HandlerThread
instância. O Handler
deve ser criado durante a execução no HandlerThread
, embora uma vez criado, não haja nenhuma restrição sobre quais threads podem usar os Handler
métodos de agendamento de ( post(Runnable)
, etc.)
O thread principal (também conhecido como thread de interface do usuário) em um aplicativo Android é configurado como um thread de manipulador antes da criação da instância do aplicativo.
Além da documentação da aula, há uma boa discussão sobre tudo isso aqui .
PS Todas as classes mencionadas acima estão no pacote android.os
.
MessageQueue
Android documenta que a MessageQueue
é uma " classe de baixo nível que contém a lista de mensagens a serem despachadas por a Looper
. "
É amplamente conhecido que é ilegal atualizar componentes da IU diretamente de threads diferentes do thread principal no Android. Este documento android ( Handling Expensive Operations in the UI Thread ) sugere as etapas a seguir se precisarmos iniciar um thread separado para fazer algum trabalho caro e atualizar a IU depois de concluído. A ideia é criar um objeto Handler associado ao thread principal e postar um Runnable nele no momento apropriado. Isso Runnable
será invocado no thread principal . Este mecanismo é implementado com as classes Looper e Handler .
A Looper
classe mantém um MessageQueue , que contém uma lista de mensagens . Um caractere importante do Looper é que ele está associado ao segmento no qual o Looper
é criado . Esta associação é mantida para sempre e não pode ser quebrada nem alterada. Observe também que um tópico não pode ser associado a mais de um Looper
. Para garantir essa associação, ele Looper
é armazenado no armazenamento local do segmento e não pode ser criado por meio de seu construtor diretamente. A única maneira de criá-lo é chamar o método estático prepare on Looper
. método de preparação examina primeiro ThreadLocaldo thread atual para ter certeza de que já não existe um Looper associado ao thread. Após o exame, um novo Looper
é criado e salvo no ThreadLocal
. Tendo preparado o Looper
, podemos chamar o método de loop nele para verificar se há novas mensagens e ter Handler
que lidar com elas.
Como o nome indica, a Handler
classe é principalmente responsável por manipular (adicionar, remover, despachar) mensagens do thread atual MessageQueue
. Uma Handler
instância também está ligada a um thread. A ligação entre Handler e Thread é realizada por meio de Looper
e MessageQueue
. A Handler
está sempre associado a a Looper
e, subsequentemente, ao thread associado a Looper
. Ao contrário Looper
, várias instâncias de Handler podem ser vinculadas ao mesmo thread. Sempre que chamamos post ou quaisquer métodos semelhantes no Handler
, uma nova mensagem é adicionada ao associado MessageQueue
. O campo de destino da mensagem é definido para a Handler
instância atual . Quando oLooper
recebeu essa mensagem, ele invoca dispatchMessage no campo de destino da mensagem, de modo que a mensagem seja roteada de volta para a instância Handler a ser tratada, mas no encadeamento correto. As relações entre Looper
, Handler
e MessageQueue
é mostrada abaixo:
Vamos começar com o Looper. Você pode entender a relação entre Looper, Handler e MessageQueue mais facilmente quando entender o que é Looper. Além disso, você pode entender melhor o que é Looper no contexto da estrutura da GUI. Looper é feito para fazer 2 coisas.
1) Looper transforma um thread normal , que termina quando seu run()
método retorna, em algo que é executado continuamente até que o aplicativo Android seja executado , o que é necessário na estrutura da GUI (Tecnicamente, ele ainda termina quando o run()
método retorna. Mas deixe-me esclarecer o que quero dizer, abaixo).
2) O Looper fornece uma fila onde os trabalhos a serem realizados são enfileirados, o que também é necessário na estrutura da GUI.
Como você deve saber, quando um aplicativo é iniciado, o sistema cria um thread de execução para o aplicativo, chamado “principal”, e os aplicativos Android normalmente são executados inteiramente em um único thread por padrão, o “thread principal”. Mas o tópico principal não é um tópico especial secreto . É apenas um thread normal que você também pode criar com o new Thread()
código, o que significa que ele termina quando o run()
método retorna! Pense no exemplo abaixo.
public class HelloRunnable implements Runnable {
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String args[]) {
(new Thread(new HelloRunnable())).start();
}
}
Agora, vamos aplicar este princípio simples ao aplicativo Android. O que aconteceria se um aplicativo Android fosse executado em uma thread normal? Um thread chamado "principal" ou "UI" ou o que quer que inicie o aplicativo e desenhe toda a UI. Assim, a primeira tela é exibida aos usuários. E agora? O tópico principal termina? Não, não deveria. Deve esperar até que os usuários façam algo, certo? Mas como podemos alcançar esse comportamento? Bem, podemos tentar com Object.wait()
ouThread.sleep()
. Por exemplo, o thread principal termina seu trabalho inicial para exibir a primeira tela e adormece. Ele acorda, o que significa interrompido, quando uma nova tarefa a ser feita é buscada. Até aqui tudo bem, mas neste momento precisamos de uma estrutura de dados em forma de fila para armazenar vários trabalhos. Pense em um caso em que um usuário toca a tela em série e uma tarefa leva mais tempo para ser concluída. Portanto, precisamos ter uma estrutura de dados para manter as tarefas a serem executadas da maneira primeiro a entrar, primeiro a sair. Além disso, você pode imaginar, implementar um thread sempre em execução e processar o trabalho quando chegado usando interrupção não é fácil e leva a um código complexo e muitas vezes impossível de manter. Preferimos criar um novo mecanismo para esse propósito, e é disso que se trata o Looper . O documento oficial da classe Looperdiz, "Threads por padrão não têm um loop de mensagem associado a eles", e Looper é uma classe "usada para executar um loop de mensagem para um thread". Agora você pode entender o que isso significa.
Vamos passar para Handler e MessageQueue. Primeiro, MessageQueue é a fila que mencionei acima. Ele reside dentro de um Looper, e é isso. Você pode verificar isso com o código-fonte da classe Looper . A classe Looper tem uma variável de membro MessageQueue.
Então, o que é Handler? Se houver uma fila, deve haver um método que nos permita enfileirar uma nova tarefa na fila, certo? É isso que Handler faz. Podemos enfileirar uma nova tarefa em uma fila (MessageQueue) usando vários post(Runnable r)
métodos. É isso aí. Isso é tudo sobre Looper, Handler e MessageQueue.
Minha última palavra é, então, basicamente, Looper é uma classe feita para resolver um problema que ocorre no framework GUI. Mas esse tipo de necessidade também pode acontecer em outras situações. Na verdade, é um padrão bastante famoso para aplicação de multithreads, e você pode aprender mais sobre ele em "Programação simultânea em Java" por Doug Lea (especialmente, o capítulo 4.1.4 "Threads de trabalho" seria útil). Além disso, você pode imaginar que esse tipo de mecanismo não é único no framework Android, mas todos os frameworks GUI podem precisar de algo semelhante a este. Você pode encontrar quase o mesmo mecanismo na estrutura Java Swing.
MessageQueue
: É uma classe de baixo nível que contém a lista de mensagens a serem despachadas por a Looper
. As mensagens não são adicionadas diretamente a a MessageQueue
, mas sim por meio de Handler
objetos associados a Looper
. [ 3 ]
Looper
: Faz um loop em um MessageQueue
que contém as mensagens a serem despachadas. A tarefa real de gerenciamento da fila é feita pelo Handler
responsável por lidar (adicionar, remover, despachar) mensagens na fila de mensagens. [ 2 ]
Handler
: Permite enviar e processar Message
e Runnable
objetos associados a um thread MessageQueue
. Cada instância de Handler está associada a um único thread e à fila de mensagens desse thread. [ 4 ]
Quando você cria um novo Handler
, ele é vinculado ao thread / fila de mensagens do thread que o está criando - desse ponto em diante, ele entregará mensagens e executáveis para essa fila de mensagens e os executará à medida que saem da fila de mensagens .
Observe a imagem abaixo [ 2 ] para melhor compreensão.
Estendendo a resposta, por @K_Anas, com um exemplo, Como afirmou
É amplamente conhecido que é ilegal atualizar componentes da IU diretamente de threads diferentes do thread principal no Android.
por exemplo, se você tentar atualizar a IU usando Thread.
int count = 0;
new Thread(new Runnable(){
@Override
public void run() {
try {
while(true) {
sleep(1000);
count++;
textView.setText(String.valueOf(count));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
).start();
seu aplicativo irá travar com exceção.
android.view.ViewRoot $ CalledFromWrongThreadException: apenas o encadeamento original que criou uma hierarquia de visualização pode tocar suas visualizações.
em outras palavras, você precisa usar o Handler
que mantém a referência à tarefa MainLooper
ie Main Thread
ou UI Thread
e pass como Runnable
.
Handler handler = new Handler(getApplicationContext().getMainLooper);
int count = 0;
new Thread(new Runnable(){
@Override
public void run() {
try {
while(true) {
sleep(1000);
count++;
handler.post(new Runnable() {
@Override
public void run() {
textView.setText(String.valueOf(count));
}
});
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
).start() ;