English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Explicação detalhada do mecanismo de mensagem do Android e exemplos de código

Mecanismo de mensagens do Android

1.Resumo

Quando um aplicativo Android é iniciado, ele possui por padrão uma thread principal (UI thread), na qual está associada uma fila de mensagens (MessageQueue). Todas as operações são encapsuladas em uma fila de mensagens e entregues para a thread principal para serem processadas. Para garantir que a thread principal não saia, as operações da fila de mensagens são colocadas em um loop infinito, o que faz com que o programa execute um loop infinito. Em cada iteração do loop, uma mensagem é extraída da fila interna, e a função de tratamento de mensagem correspondente (handlerMessage) é chamada. Após a conclusão de uma mensagem, o loop continua. Se a fila de mensagens estiver vazia, a thread ficará bloqueada esperando. Portanto, ela não sairá. Como mostrado na figura a seguir:

Quais são as relações entre Handler, Looper e Message?

Para completar operações de longa duração em threads secundárias, muitas vezes é necessário atualizar a UI, e a mais comum é usar o Handler para postar uma mensagem na thread UI, e então tratá-la no método handlerMessage do Handler. Cada Handler está associado a uma fila de mensagens (MessageQueue), e o Looper é responsável por criar uma MessageQueue, e cada Looper está associado a um thread (Looper encapsulado por ThreadLocal). Pelo padrão, a MessageQueue tem apenas uma, que é a fila de mensagens da thread principal.

Aquí está o princípio básico do mecanismo de mensagens do Android. Se quiser saber mais detalhes, vamos começar com o código-fonte.

2.Análise de código-fonte

(1)Iniciar a loop de mensagens Looper na thread principal ActivityThread

public final class ActivityThread {
  public static void main(String[] args) {
    //Código omitido
    //1.Criar o Looper da loop de mensagens
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
      sMainThreadHandler = thread.getHandler();
    }
    AsyncTask.init();
    //2.Executar a loop de mensagens
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
  }
}

ActivityThread cria a fila de mensagens da thread principal através do método Looper.prepareMainLooper(), e, finalmente, executa Looper.loop() para iniciar a fila de mensagens. O Handler associado à fila de mensagens e ao thread.

(2Handler associado à fila de mensagens e ao thread

public Handler(Callback callback, boolean async) {}}
    //Código omitido
    //Obter Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
        "Can't create handler inside thread that has not called Looper.prepare()
    }
    //Obter a fila de mensagens
    mQueue = mLooper.mQueue;
  }

O Handler interna utiliza o método Looper.getLooper() para obter o objeto Looper e associá-lo, além de obter a fila de mensagens. Então, como funciona Looper.getLooper()?

  public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
  }
  public static @NonNull MessageQueue myQueue() {
    return myLooper().mQueue;
  }
  public static void prepare() {
    prepare(true);
  }
  //Configurar um Looper para a thread atual
  private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
      throw new RuntimeException("Apenas um Looper pode ser criado por thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
  }
  //Configurar o Looper da thread UI
  public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
      if (sMainLooper != null) {
        throw new IllegalStateException("O main Looper já foi preparado.");
      }
      sMainLooper = myLooper();
    }
  }

No objeto Looper, o método myLooper(), obtido por meio de sThreadLocal.get(), é chamado no prepareMainLooper(), onde o método prepare() é chamado. Neste método, é criado um objeto Looper e este objeto é configurado em sThreadLocal(). Dessa forma, a fila está associada à thread. Através do método sThreadLocal.get(), garante-se que diferentes threads não possam acessar a fila de mensagens da outra thread.

Por que o Handler que atualiza a UI deve ser criado na thread principal?

Porque o Handler deve estar associado à fila de mensagens da thread principal, para que o handlerMessage execute na thread UI, neste momento a thread UI é segura.

(3)loop de mensagens, processamento de mensagens

A criação do loop de mensagens é feita através do método Looper.loop(). O código-fonte é o seguinte:

/**
   * Execute a fila de mensagens nessa thread. Certifique-se de chamar
   * {@link #quit()} para encerrar o loop.
   */
  public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
      throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //1.obter a fila de mensagens
    final MessageQueue queue = me.mQueue;
    //2.loop infinito, ou seja, loop de mensagens
    for (;;) {
      //3.obter mensagem, pode bloquear
      Message msg = queue.next(); // pode bloquear
      if (msg == null) {
        // Sem mensagem indica que a fila de mensagens está saindo.
        return;
      }
      //4.processar mensagem
      msg.target.dispatchMessage(msg);
      //reciclar a mensagem
      msg.recycleUnchecked();
    }
  }

Da análise do programa acima, podemos ver que o método loop() na verdade é um loop infinito, coletando mensagens uma por uma da fila de mensagens e, em seguida, processando-as. Para Looper: cria-se um objeto Looper (a fila de mensagens está encapsulada no objeto Looper) usando Looper.prepare() e armazenando em sThreadLocal, e, em seguida, realiza-se o loop de mensagens usando Looper.loop(), estes dois passos geralmente aparecem em pares.

public final class Message implements Parcelable {
  //processamento de target
  Handler target; 
  //callback do tipo Runnable
  Runnable callback;
  //A próxima mensagem, a fila de mensagens é armazenada de forma encadeada
  Message next;
}

Da análise do código-fonte, podemos ver que 'target' é do tipo Handler. Na verdade, é uma rotação, enviando mensagens para a fila de mensagens através de Handler, e a fila de mensagens distribui as mensagens para o Handler para serem processadas. Dentro da classe Handle:

//função de processamento de mensagens, subclasses sobrescrevem
public void handleMessage(Message msg) {
}
private static void handleCallback(Message message) {
    message.callback.run();
  }
//distribuição de mensagens
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
      handleCallback(msg);
    } else {
      if (mCallback != null) {
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      handleMessage(msg);
    }
  }

A partir do programa acima, podemos ver que dispatchMessage é apenas um método de distribuição, se o callback do tipo Runnable estiver vazio, executaremos handleMessage para lidar com a mensagem, este método está vazio, então escreveremos o código de atualização da UI na função; se o callback não estiver vazio, executaremos handleCallback para lidar, este método chamará o método run do callback. Na verdade, isso são dois tipos de distribuição do Handler, por exemplo, se post(Runnable callback) não estiver vazio, quando usamos sendMessage com Handler geralmente não configuramos callback, então executamos handleMessage.

 public final boolean post(Runnable r)
  {
    return sendMessageDelayed(getPostMessage(r), 0);
  }
  public String getMessageName(Message message) {
    if (message.callback != null) {
      return message.callback.getClass().getName();
    }
    return "0x" + Integer.toHexString(message.what);
  }
  public final boolean sendMessageDelayed(Message msg, long delayMillis)
  {
    if (delayMillis < 0) {
      delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
      RuntimeException e = new RuntimeException(
          this + "sendMessageAtTime() chamada com sem mQueue");
      Log.w("Looper", e.getMessage(), e);
      return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
  }

Do programa acima, podemos ver que, ao usar post(Runnable r), o Runnable é empacotado em um objeto Message e o objeto Runnable é configurado como o callback do objeto Message, e, em seguida, o objeto é inserido na fila de mensagens. A implementação de sendMessage é semelhante:

public final boolean sendMessage(Message msg)
  {
    return sendMessageDelayed(msg, 0);
  }

Seja para postar um Runnable ou um Message, sempre será chamado o método sendMessageDelayed(msg, time). O Handler finalmente adiciona a mensagem à MessageQueue, e o Looper continua lendo mensagens da MessageQueue e chamando o método dispatchMessage do Handler para distribuir as mensagens, então as mensagens são continuamente geradas, adicionadas à MessageQueue e processadas pelo Handler, making o aplicativo Android funcionar.

3.Verificação

new Thread(){
  Handler handler = null;
  public void run () {
    handler = new Handler();
  ;
.start();

Há algum problema com o código acima?

O objeto Looper é ThreadLocal, o que significa que cada thread usa seu próprio Looper, que pode estar vazio. No entanto, ao criar um objeto Handler em uma subthread, se o Looper estiver vazio, ocorrerá uma exceção.

public Handler(Callback callback, boolean async) {}}
    //Código omitido
    //Obter Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
        "Can't create handler inside thread that has not called Looper.prepare()
    }
    //Obter a fila de mensagens
    mQueue = mLooper.mQueue;
  }

Quando mLooper estiver vazio, lance uma exceção. Isso ocorre porque o objeto Looper não foi criado, então sThreadLocal.get() retornará null. O princípio básico do Handler é estabelecer uma conexão com MessageQueue e entregar mensagens para MessageQueue. Sem MessageQueue, o Handler não tem necessidade de existir, e MessageQueue está encapsulada no Looper, então, ao criar um Handler, o Looper não pode estar vazio. A solução é a seguinte:

new Thread(){
  Handler handler = null;
  public void run () {
    //Criar um Looper para a thread atual e associá-lo a ThreadLocal
    Looper.prepare();
    handler = new Handler();
    //Iniciar a circulação de mensagens
    Looper.loop();
  ;
.start();

Se criar um Looper sem iniciar a circulação de mensagens, embora não seja lançada uma exceção, nem o post ou sendMessage() através do handler será eficaz. Porque embora as mensagens sejam adicionadas à fila de mensagens, não foi iniciada a circulação de mensagens, portanto, não serão obtidas e executadas mensagens da fila de mensagens.

Obrigado por ler, espero que ajude a todos, obrigado pelo apoio ao site!

Você pode gostar