Android中的非同步訊息處理機制

susion發表於2019-02-27

這也是Android中老生常談的一個話題了,它本身並不是很複雜,可是面試官比較喜歡問。本文就從原始碼再簡單的理一下這個機制。也可以說是理一下HandlerLooperMessageQueue之間的關係。

單執行緒中的訊息處理機制的實現

首先我們以Looper.java原始碼中給出的一個例子來分析一下在單執行緒中如何使用這個機制:

class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();
          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };
          Looper.loop();
      }

 }
複製程式碼

Looper.prepare()

上面只涉及到HandlerLooperMessageQueue呢?我們來看一下Looper.prepare():

private static void prepare(boolean quitAllowed) {
    ...
    sThreadLocal.set(new Looper(quitAllowed));
}
複製程式碼

很簡單,即new Looper,然後把它放到ThreadLocal<Looper>中(ThreadLocal儲存線上程的私有map中)。繼續看一下Looper的構造方法:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    ...
}
複製程式碼

即,在這裡MessageQueue作為Looper的成員變數被初始化了。所以 一個Looper對應一個MessageQueue 。 ok,到這裡Looper.prepare()所涉及的邏輯以及瀏覽完畢,繼續看一下new Handler():

new Handler()

mLooper = Looper.myLooper();
if (mLooper == null) {
    throw new RuntimeException("Can`t create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
複製程式碼

Handler會持有一個Looper, 那我們看一下這個Looper來自於哪裡: Looper.myLooper()

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
複製程式碼

即會從當前執行緒的私有map中取出ThreadLocal<Looper>。所以Handler預設持有當前執行緒的Looper的引用。如果當前執行緒沒有Looper,那麼Handler就會構造失敗,丟擲異常。其實可以在構造Handler時指定一個Looper,下面會講到這個。

在持有當前執行緒的Looper的引用同時,Handler在構造時也會獲取Looper的成員變數MessageQueue,並持有它。 如果你在一個執行緒中同時new多個Handler的話,那他們的關係如下圖所示:

Android中的非同步訊息處理機制

即:

  1. LooperMessageQueue存放在當前執行緒的ThreadLocal
  2. Handler持有當前執行緒的LooperMessageQueue

Looper.loop()

這個方法可以說是核心了:

public static void loop( ) {
    final Looper me = myLooper();
    //...
    final MessageQueue queue = me.mQueue;
    //...
    for (;;) {
        Message msg = queue.next(); // might block
        //…
        msg.target.dispatchMessage(msg);
        //...
    }
}
複製程式碼

Looper不斷檢查MessageQueue中是否有訊息Message,並呼叫msg.target.dispatchMessage(msg)處理這個訊息。 那msg.target是什麼呢?其實它是Handler的引用。

msg.target.dispatchMessage(msg)會導致Handler.handleMessage()的呼叫,其實到這裡單執行緒中的訊息處理機制模型已經有了一個大致的輪廓了,接下來就需要弄清楚

  1. msg.target是在哪裡賦值的?
  2. 訊息是如何插入到MessageQueue中的?

傳送一個訊息

Handler傳送一個最簡單的訊息為例:

handler.sendEmptyMessage(0)
複製程式碼

這個方法最終呼叫到的核心邏輯是:

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);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
複製程式碼

Handler會把Messagetarget設定為自己,並把訊息放入到當前執行緒的MessageQueue中。

這樣Looper.loop()方法中就可以從MessageQueue中取出訊息,並把訊息傳送到msg.target(Handler)去處理,其實msg.target.dispatchMessage(msg)會呼叫Handler.handleMessage()方法。

到這裡就完成了整個訊息處理模型的分析。其實 整個Android主執行緒的UI更新都是建立在這個模型之上的

用下面這個圖總結一下整個執行機制:

Android中的非同步訊息處理機制

多執行緒中的訊息處理機制的應用

舉一個最典型的例子: 在下載執行緒中完成了下載,通知主執行緒更新UI。怎麼做呢?

明白了上面Handler/MessageQueue/Looper的關係後,我們只需要往主執行緒的MessageQueue中傳送一個更新UI的Message即可,那怎麼往主執行緒發訊息呢?

指定Handler所依附的Looper

對,只需要在構造Hander時把主執行緒的Looper傳遞給它即可:

    downLoadHandler = Handler(Looper.getMainLooper())
複製程式碼

這樣downLoadHandler.sendEmptyMessage(2)就會傳送到主執行緒的MessageQueue中。handler.handleMessage()也將會在主執行緒中回撥。

其實更簡單的是在主執行緒中儲存一個Handler成員變數,子執行緒直接拿這個Handler發訊息即可。

但在多執行緒使用Handler時因為涉及到執行緒切換和非同步,要注意記憶體洩漏和物件可用性檢查。比如在更新UI時Activityfinish狀態,這時候就需要你的update是否可以繼續執行等。

執行緒通訊

運用這個模型我們可以很方便的進行執行緒通訊:

  1. 需要通訊的兩個執行緒都建立Looper
  2. 雙方分別持有對方的handler,然後互相傳送訊息

歡迎關注我的Android進階計劃看更多幹貨

微信公眾號:

Android中的非同步訊息處理機制

相關文章