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

Spiegazione dettagliata del meccanismo di messaggi di Android e esempi di codice

Meccanismo di messaggi Android

1. Sommario

Quando un'applicazione Android viene avviata, viene predefinito un thread principale (UI thread), in cui viene associata una coda di messaggi (MessageQueue), tutte le operazioni vengono impacchettate in una coda di messaggi e poi consegnate al thread principale per essere gestite. Per garantire che il thread principale non si chiuda, le operazioni sulla coda di messaggi vengono messe in un ciclo infinito, quindi il programma esegue un ciclo infinito, ogni volta che il ciclo viene eseguito, preleva un messaggio dalla coda interna, quindi chiama la funzione di gestione del messaggio (handlerMessage), dopo aver completato un messaggio, continua il ciclo. Se la coda di messaggi è vuota, il thread si blocca in attesa. Di conseguenza, non si chiude. Come mostrato nella figura seguente:

Handler、Looper、Message之间有什么关系?

在子线程中完成耗时操作,很多情况下需要更新UI,最常用的就是通过Handler将一个消息Post到UI线程中,然后再在Handler的handlerMessage方法中进行处理。而每个Handler都会关联一个消息队列(MessageQueue),Looper负责的就是创建一个MessageQueue,而每个Looper又会关联一个线程(Looper通过ThreadLocal封装)。默认情况下,MessageQueue只有一个,即主线程的消息队列。

上面就是Android消息机制的基本原理,如果想了解更详细,我们从源码开始看。

2.源码解读

(1) ActivityThread在主线程中启动消息循环Looper

public final class ActivityThread {
  public static void main(String[] args) {
    //Codice omesso
    //1.创建消息循环的Looper
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
      sMainThreadHandler = thread.getHandler();
    }
    AsyncTask.init();
    //2.执行消息循环
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
  }
}

ActivityThread通过Looper.prepareMainLooper()创建主线程的消息队列,最后执行Looper.loop()来启动消息队列。Handler关联消息队列和线程。

(2) Handler关联消息队列和线程

public Handler(Callback callback, boolean async) {
    //Codice omesso
    //Ottieni Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
        "Non è possibile creare un handler all'interno di un thread che non ha chiamato Looper.prepare()
    }
    //Ottieni la coda dei messaggi
    mQueue = mLooper.mQueue;
  }

Handler通过Looper.getLooper()方法在内部获取Looper对象,并与之关联,并获取消息队列。那么Looper.getLooper()是如何工作的呢?

  public static @Nullable Looper myLooper() {}}
    return sThreadLocal.get();
  }
  public static @NonNull MessageQueue myQueue() {
    return myLooper().mQueue;
  }
  public static void prepare() {
    prepare(true);
  }
  // Impostare un Looper per la current thread
  private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
      throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
  }
  // Impostare il Looper della thread dell'UI
  public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
      if (sMainLooper != null) {
        throw new IllegalStateException("The main Looper has already been prepared.");
      }
      sMainLooper = myLooper();
    }
  }

Nel ciclo Looper, il metodo myLooper(), tramite sThreadLocal.get(), viene utilizzato per ottenere, nel metodo prepareMainLooper() viene chiamato il metodo prepare(), in questo metodo viene creato un oggetto Looper e l'oggetto viene impostato su sThreadLocal(). Così la coda è associata alla thread. Attraverso il metodo sThreadLocal.get(), si garantisce che diverse thread non possano accedere alla coda di messaggi dell'altra thread.

Perché il Handler che deve aggiornare l'UI deve essere creato nella thread principale?

Poiché il Handler deve essere associato alla coda di messaggi della thread principale, in modo che handlerMessage venga eseguito nella thread principale dell'UI, in questo momento la thread principale dell'UI è sicura.

(3) ciclo di messaggi, gestione dei messaggi

La creazione del ciclo di messaggi avviene tramite il metodo Looper.loop(). Il codice sorgente è il seguente:

/**
   * Esegui la coda dei messaggi in questo thread. Assicurati di chiamare
   * {@link #quit()} per terminare il ciclo.
   */
  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. Ottieni la coda dei messaggi
    final MessageQueue queue = me.mQueue;
    // 2. Ciclo infinito, ovvero ciclo dei messaggi
    for (;;) {
      // 3. Ottieni il messaggio, potrebbe bloccarsi
      Message msg = queue.next(); // potrebbe bloccarsi
      if (msg == null) {
        // Nessun messaggio indica che la coda dei messaggi sta abbandonando.
        return;
      }
      // 4. Elabora il messaggio
      msg.target.dispatchMessage(msg);
      // Recupera il messaggio
      msg.recycleUnchecked();
    }
  }

Dallo stesso programma possiamo vedere che la sostanza del metodo loop() è creare un ciclo infinito, quindi prendere messaggi uno per uno dalla coda dei messaggi e infine elaborare i messaggi. Per Looper: creare l'oggetto Looper tramite Looper.prepare() e inserirlo nella sThreadLocal, quindi eseguire il ciclo dei messaggi tramite Looper.loop(), questi due passaggi si verificano spesso insieme.

public final class Message implements Parcelable {
  // target di elaborazione
  Handler target; 
  // callback di tipo Runnable
  Runnable callback;
  // Il messaggio successivo, la coda dei messaggi è memorizzata in modo a catena
  Message next;
}

Dal codice sorgente si può vedere che target è di tipo Handler. In realtà è solo un cerchio, invia messaggi alla coda dei messaggi tramite Handler, e la coda dei messaggi distribuisce i messaggi per essere elaborati dal Handler. Nel metodo Handle:

//Funzione di elaborazione dei messaggi, sovrascritta dalla sottoclasse
public void handleMessage(Message msg) {
}
private static void handleCallback(Message message) {
    message.callback.run();
  }
//Distribuzione dei messaggi
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
      handleCallback(msg);
    }
      if (mCallback != null) {
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      handleMessage(msg);
    }
  }

Da questo programma si può vedere che dispatchMessage è solo un metodo di distribuzione, se il callback di tipo Runnable è vuoto, si esegue handleMessage per elaborare il messaggio, questo metodo è vuoto, scriveremo il codice per aggiornare l'UI in questa funzione; se il callback non è vuoto, si esegue handleCallback per elaborare, questo metodo chiama il metodo run del callback. In realtà, questi sono due tipi di distribuzione di Handler, ad esempio, post(Runnable callback) il callback non è vuoto, quando si utilizza sendMessage con Handler di solito non si imposta il callback, quindi si esegue 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() called with no mQueue\
      Log.w("Looper", e.getMessage(), e);
      return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
  }

Dal programma sopra, si può vedere che quando si invia post(Runnable r), il Runnable viene impacchettato in un oggetto Message e l'oggetto Runnable viene impostato come callback dell'oggetto Message, infine l'oggetto viene inserito nella coda dei messaggi. sendMessage ha un'implementazione simile:

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

Sia che si invii un Runnable o un Message, viene chiamato il metodo sendMessageDelayed(msg, time). L'Handler aggiunge infine il messaggio alla MessageQueue, e il Looper legge continuamente i messaggi dalla MessageQueue e chiama il metodo dispatchMessage dell'Handler per distribuire i messaggi, in modo che i messaggi vengano prodotti, aggiunti alla MessageQueue e trattati dall'Handler, facendo funzionare l'app Android.

3. Verifica

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

Il codice sopra ha problemi?

L'oggetto Looper è ThreadLocal, ovvero ogni thread utilizza il proprio Looper, che può essere vuoto. Tuttavia, quando si crea un oggetto Handler in un thread secondario, se Looper è vuoto, si verifica un'eccezione.

public Handler(Callback callback, boolean async) {
    //Codice omesso
    //Ottieni Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
        "Non è possibile creare un handler all'interno di un thread che non ha chiamato Looper.prepare()
    }
    //Ottieni la coda dei messaggi
    mQueue = mLooper.mQueue;
  }

Quando mLooper è vuoto, viene lanciata un'eccezione. Questo perché l'oggetto Looper non è stato creato, quindi sThreadLocal.get() restituisce null. Il principio di base di Handler è quello di stabilire una relazione con MessageQueue e inoltrare i messaggi a MessageQueue. Senza MessageQueue, Handler non ha senso di esistere, e MessageQueue è chiusa nel Looper. Pertanto, quando si crea un Handler, Looper non può essere vuoto. La soluzione è la seguente:

new Thread(){
  Handler handler = null;
  public void run () {
    //Crea un Looper per il thread corrente e lo binding a ThreadLocal
    Looper.prepare()
    handler = new Handler();
    //Avvia il ciclo dei messaggi
    Looper.loop();
  ;
.start();

Se si crea un Looper senza avviare il ciclo dei messaggi, anche se non si generano eccezioni, non sarà efficace passare o sendMessage() tramite handler. Poiché i messaggi vengono aggiunti alla coda dei messaggi, ma non è stato avviato il ciclo dei messaggi, quindi non vengono prelevati e eseguiti dai messaggi della coda.

Grazie per aver letto, spero che possa aiutarvi, grazie per il supporto al nostro sito!

Ti potrebbe interessare