細致一點地看看 Handler 和它的伙伴們

Handler 對于 Android 開發來說簡直就是家常便飯,它的原理自然都很熟悉,這篇文章不會宏觀地去介紹它的原理,而是細節深入到各個組成。

目錄

關系

開始深入細節的時候,我們可以先復習下 Handler 、Looper 和 MessageQueue 三者的關系。

  1. Handler 必須在 Looper.prepare() 之后才能創建使用
  2. Looper 與當前線程關聯,并且管理著一個 MessageQueue
  3. Message 是實現 Parcelable 接口的類
  4. 以一個線程為基準,他們的數量級關系是:
    Handler(N) : Looper(1) : MessageQueue(1) : Thread(1)

他們的調用關系可以參考這張圖:


分析

0x01
public Handler(Callback callback, boolean async) {
    // 代碼省略

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

從 Handler 默認的構造函數我們可以看到,Handler 內部會通過 Looper.myLooper() 來獲取 Looper 對象,從而與之關聯。

0x02

我們之前已經知道 Looper 管理著消息隊列,從這里深入進去看看是如何跟 MessageQueue 建立聯系。

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

public static void prepare() {
    prepare(true);
}

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

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

Looper.myLooper() 里我們看到,Looper 是通過 sThreadLocal.get() 來獲取,那么我們又是何時將 Looper 設置給 sThreadLocal 的呢?答案就在 prepare() 方法里。
我們看到 sThreadLocal.set(new Looper(quitAllowed)); 實例化了一個 Looper 對象給 sThreadLocal 并且一個線程只有一個 Looper 。

同時我也貼出了 prepareMainLooper() 方法,根據名字大家都可以猜到,這個方法就是在 Android 主線程(UI)線程調用的方法,而在這個方法里也調用了 prepare(false) 我們看到這里傳入的是 false ,表明主線程這里的 Looper 是無法執行 quit() 方法。
我在這里貼出 ActivityThread 的 Main() 方法的部分代碼,這也是我們程序的入口:

public static void main(String[] args) {
    // 代碼省略

    Looper.prepareMainLooper(); // 創建消息循環 Looper

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler(); // UI 線程的 Handler
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    Looper.loop(); // 執行消息循環

}

在這里我們更清楚了為什么可以直接在主線程創建 Handler ,而不會發生異常。

以上,我們明白了 Looper 是通過 prepare() 方法與線程建立聯系,同時不同線程是無法訪問對方的消息隊列。

為什么 Handler 要在主線程創建才能更新 UI 呢?

因為 Handler 要與主線程的消息隊列關聯上,這樣 handleMessage() 才會執行在 UI 線程。

0x03

Looper 的核心其實是它循環取出消息的代碼:

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    // 死循環
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        /// Handler msg.target
        msg.target.dispatchMessage(msg); // 派發消息

        // 代碼省略

        msg.recycleUnchecked();
    }
}

從上面代碼我們可以看到,Looper 在 loop() 方法里建立了一個死循環,通過消息隊列里不斷的取出消息,交給 Handler 去處理。

這個時候你可能會有一個問題:

Android 中為什么主線程不會因為 Looper.loop() 里的死循環卡死?

我比較推薦 Gityuan 的回答

回到我們這里,在循環中是通過 msg.target.dispatchMessage(msg); 派發消息。其中 msg 是 Message 類型,簡單看看它的成員:

public final class Message implements Parcelable {
    Handler target;
    Runnable callback;
    Message next;

    public Object obj;
    public int arg1;
    public int arg2;

    // 代碼省略
}

可以知道消息隊列是鏈表實現的,并且 target 是 Handler 類型。

現在就可以連通了,通過 Handler 將 Message 投遞給消息隊列(鏈表),Looper.loop() 循環從消息隊列里取出消息,又將消息分發給 Handler 去處理。通過這個 target 我們也可以知道一個小細節,Handler 只能處理自己所發出的消息。

0x04

理解清楚之后我們跟著順序,看看 Handler 是如何處理和分發消息的。

// 處理消息方法,交給子類復寫
public void handleMessage(Message msg) {
}

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}

我們看到 dispatchMessage() 只是一個分發方法,如果 Runnable 類型的 callback 為空,則執行 handleMessage(msg) 處理信息,該方法為空,是交給子類進行復寫,并且執行線程是在 Handler 所創建的線程。
如果 callback 不為空,則會執行 handleCallback(msg) 來處理信息,該方法會調用 callback 的 run() 方法。

其實說簡單一點,就是 Handler 的兩種分發類型。
一種是 post(r) 另一種是 sendMessage(msg)

我們具體看看這兩個方法:

public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r){
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

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

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

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this; // 與當前 Handler 綁定
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

做了一個導圖,方便理解下:


從中我們可以看到,在 post(r) 時,會將 Runnable 包裝成 Message 對象,并且賦值給 Message 的 callback 字段,最后跟 sendMessage(msg) 方法一樣將消息插入隊列。

根據代碼和導圖,無論是 post(r) 還是 sendMessage(msg) 都會最終調用 sendMessageAtTime(msg,time)

總結

Handler 最終將消息追加到 MessageQueue 中,而 Looper 不斷的從 MessageQueue 中讀取消息,并且調用 Handler 的 dispatchMessage 分發消息,最后交給上層處理消息。

參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容