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.

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


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.


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


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() ;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.