Qual é a relação entre Looper, Handler e MessageQueue no Android?

95

Fui verificar a documentação / guia oficial Android para Looper, Handlere MessageQueue. Mas eu não consegui entender. Eu sou novo no Android e fiquei muito confuso com esses conceitos.

Blake
fonte

Respostas:

103

A Looperé um loop de tratamento de mensagens: ele lê e processa itens de a MessageQueue. A Looperclasse é 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 - Looperprincipalmente postando mensagens e Runnableobjetos 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 Handlerobjeto (ou objetos) pelo qual outros threads podem interagir com a HandlerThreadinstância. O Handlerdeve ser criado durante a execução no HandlerThread, embora uma vez criado, não haja nenhuma restrição sobre quais threads podem usar os Handlermé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.

Ted Hopp
fonte
@Ted Hopp - A fila de mensagens do Looper é diferente da fila de mensagens do Thread?
CopsOnRoad
2
@Jack - Eles são a mesma coisa. A APIMessageQueue Android documenta que a MessageQueueé uma " classe de baixo nível que contém a lista de mensagens a serem despachadas por a Looper. "
Ted Hopp
95

É 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 Runnableserá invocado no thread principal . Este mecanismo é implementado com as classes Looper e Handler .

A Looperclasse 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 Handlerque lidar com elas.

Como o nome indica, a Handlerclasse é principalmente responsável por manipular (adicionar, remover, despachar) mensagens do thread atual MessageQueue. Uma Handlerinstância também está ligada a um thread. A ligação entre Handler e Thread é realizada por meio de Loopere MessageQueue. A Handlerestá sempre associado a a Loopere, 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 Handlerinstância atual . Quando oLooperrecebeu 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, Handlere MessageQueueé mostrada abaixo:

insira a descrição da imagem aqui

K_Anas
fonte
5
Obrigado! mas qual é o ponto se o manipulador primeiro postar a mensagem na fila de mensagens e, em seguida, tratar a mensagem da mesma fila? por que simplesmente não trata a mensagem diretamente?
Blake
4
@Blake b / c você está postando de um tópico (tópico não looper), mas lidando com a mensagem em outro tópico (tópico looper)
numan salati
Muito melhor do que o que está sendo documentado em developer.android.com - mas seria bom ver o código do diagrama que você forneceu.
tfmontague
@numansalati - O manipulador não pode postar mensagens do tópico do looper?
CopsOnRoad
78

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.

김준호
fonte
4
Melhor resposta. Aprendeu mais com esta explicação detalhada. Gostaria de saber se há alguma postagem no blog que seja mais detalhada.
capt.swag
As mensagens podem ser adicionadas ao MessageQueue sem usar o Handler?
CopsOnRoad
@CopsOnRoad não, eles não podem ser adicionados diretamente.
Faisal Naseer
Fez meu dia ... muito amor para você :)
Rahul Matte
26

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 Handlerobjetos associados a Looper. [ 3 ]

Looper: Faz um loop em um MessageQueueque contém as mensagens a serem despachadas. A tarefa real de gerenciamento da fila é feita pelo Handlerresponsável por lidar (adicionar, remover, despachar) mensagens na fila de mensagens. [ 2 ]

Handler: Permite enviar e processar Messagee Runnableobjetos 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.

insira a descrição da imagem aqui

AnV
fonte
0

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 Handlerque mantém a referência à tarefa MainLooper ie Main Threadou UI Threade 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() ;
Faisal Naseer
fonte