深入理解 Handler 消息機制

記得很多年前的一次面試中,面試官問了這麼一個問題,你在項目中一般如何實現線程切換? 他的本意應該是考察 RxJava 的使用,只是我的答案是 Handler,他也就沒有再追問下去了。在早期 Android 開發的荒蕪時代,Handler 的確承擔了項目中大部分的線程切換工作,通常包括子線程更新 UI 和消息傳遞。不光在我們自己的應用中,在整個 Android 體系中,Handler 消息機制也是極其重要的,不亞於 Binder 的地位。 ActivityThread.java 中的內部類 H 就是一個 Handler,它內部定義了幾十種消息類型來處理一些系統事件。

Handler 的重要性毋庸置疑,今天就通過 AOSP 源碼來深入學習 Handler。相關類的源碼包含註釋均已上傳到我的 Github 倉庫 android_9.0.0_r45 :

Handler.java

Looper.java

Message.java

MessageQueue.java

Handler

Handler 用來發送和處理線程對應的消息隊列 MessageQueue 中存儲的 Message。每個 Handler 實例對應一個線程以及該線程的消息隊列。當你創建一個新的 Handler,它會綁定創建它的線程和消息隊列,然後它會向消息隊列發送 Message 或者 Runnable,並且在它們離開消息隊列時執行。

Handler 有兩個主要用途:

  1. 規劃 Message 或者 Runnable 在未來的某個時間點執行
  2. 在另一個線程上執行代碼

以上翻譯自官方註釋。說白了,Handler 只是安卓提供給開發者用來發送和處理事件的,而消息如何存儲,消息如何循環取出,這些邏輯則交給 MessageQueueLooper 來處理,使用者並不需要關心。但要真正瞭解 Handler 消息機制,認真讀一遍源碼就必不可少了。

構造函數

Handler 的構造函數大致上可以分爲兩類,先來看第一類:

public Handler() {
    this(null, false);
}

public Handler(Callback callback) {
    this(callback, false);
}

public Handler(Callback callback, boolean async) {
    // 如果是匿名類、內部類、本地類,且沒有使用 static 修飾符,提示可能導致內存泄漏
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    // 從當前線程的 ThreadLocal獲取 Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {  // 創建 Handler 之前一定要先創建 Looper。主線程已經自動爲我們創建。
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue; // Looper 持有一個 MessageQueue
    mCallback = callback; // handleMessage 回調
    mAsynchronous = async; // 是否異步處理
}

這一類構造函數最終調用的都是兩個參數的方法,參數中不傳遞 Looper,所以要顯式檢查是否已經創建 Looper。創建 Handler 之前一定要先創建 Looper,否則會直接拋出異常。在主線程中 Looper 已經自動創建好,無需我們手動創建,在 ActivityThread.javamain() 方法中可以看到。Looper 持有一個消息隊列 MessageQueue,並賦值給 Handler 中的 mQueue 變量。Callback 是一個接口,定義如下:

public interface Callback {
    public boolean handleMessage(Message msg);
}

通過構造器參數傳入 CallBack 也是 Handler 處理消息的一種實現方式。

再回頭看一下在上面的構造函數中是如何獲取當前線程的 Looper 的?

 mLooper = Looper.myLooper(); // 獲取當前線程的 Looper

這裏先記着,回頭看到 Looper 源碼時再詳細解析。

看過 Handler 的第一類構造函數,第二類其實就很簡單了,只是多了 Looper 參數而已:

public Handler(Looper looper) {
    this(looper, null, false);
}
    
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}
    
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

直接賦值即可。

除此之外還有幾個標記爲 @hide 的構造函數就不作說明了。

發送消息

發送消息大家最熟悉的方法就是 sendMessage(Message msg) 了,可能有人不知道其實還有 post(Runnable r) 方法。雖然方法名稱不一樣,但最後調用的都是同一個方法。

sendMessage(Message msg)
sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what, long delayMillis)
sendEmptyMessageAtTime(int what, long uptimeMillis)
sendMessageAtTime(Message msg, long uptimeMillis)

幾乎所有的 sendXXX() 最後調用的都是 sendMessageAtTime() 方法。

post(Runnable r)
postAtTime(Runnable r, long uptimeMillis)
postAtTime(Runnable r, Object token, long uptimeMillis)
postDelayed(Runnable r, long delayMillis)
postDelayed(Runnable r, Object token, long delayMillis)

所有的 postXXX() 方法都是調用 getPostMessage() 將 參數中的 Runnable 包裝成 Message,再調用對應的 sendXXX() 方法。看一下 getPostMessage() 的代碼:

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

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

主要是把參數中的 Runnable 賦給 Message 的 callback 屬性。

殊途同歸,發送消息的重任最後都落在了 sendMessageAtTime() 身上。

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); // 調用 Messagequeue 的 enqueueMessage() 方法
}

Handler 就是一個撒手掌櫃,發送消息的任務轉手又交給了 MessageQueue 來處理。

再額外提一點,enqueueMessage() 方法中的參數 uptimeMillis 並不是我們傳統意義上的時間戳,而是調用 SystemClock.updateMillis() 獲取的,它表示自開機以來的毫秒數。

MessageQueue

enqueueMessage()

Message 的入隊工作實際上是由 MessageQueue 通過 enqueueMessage() 函數來完成的。

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) { // msg 必須有 target
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) { // msg 不能正在被使用
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) { // 正在退出,回收消息並直接返回
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            // 插入消息隊列頭部,需要喚醒隊列
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) { // 按消息的觸發時間順序插入隊列
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

從源碼中可以看出來,MessageQueue 是用鏈表結構來存儲消息的,消息是按觸發時間的順序來插入的。

enqueueMessage() 方法是用來存消息的,既然存了,肯定就得取,這靠的是 next() 方法。

next()

Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        // 阻塞方法,主要是通過 native 層的 epoll 監聽文件描述符的寫入事件來實現的。
        // 如果 nextPollTimeoutMillis = -1,一直阻塞不會超時。
        // 如果 nextPollTimeoutMillis = 0,不會阻塞,立即返回。
        // 如果 nextPollTimeoutMillis > 0,最長阻塞nextPollTimeoutMillis毫秒(超時),如果期間有程序喚醒會立即返回。
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                // msg.target == null表示此消息爲消息屏障(通過postSyncBarrier方法發送來的)
                // 如果發現了一個消息屏障,會循環找出第一個異步消息(如果有異步消息的話),
                // 所有同步消息都將忽略(平常發送的一般都是同步消息)
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // 消息觸發時間未到,設置下一次輪詢的超時時間
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    // 得到 Message
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse(); // 標記 FLAG_IN_USE
                    return msg;
                }
            } else {
                // No more messages.
                // 沒有消息,會一直阻塞,直到被喚醒
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            // Idle handle 僅當隊列爲空或者隊列中的第一個消息將要執行時纔會運行
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                // 沒有 idle handler 需要運行,繼續循環
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        // 只有第一次循環時纔會執行下面的代碼塊
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        // 將 pendingIdleHandlerCount 置零保證不再運行
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

next() 方法是一個死循環,但是當沒有消息的時候會阻塞,避免過度消耗 CPU。nextPollTimeoutMillis 大於 0 時表示等待下一條消息需要阻塞的時間。等於 -1 時表示沒有消息了,一直阻塞到被喚醒。

這裏的阻塞主要靠 native 函數 nativePollOnce() 來完成。其具體原理我並不瞭解,想深入學習的同學可以參考 Gityuan 的相關文 Android消息機制2-Handler(Native層)

MessageQueue 提供了消息入隊和出隊的方法,但它自己並不是自動取消息。那麼,誰來把消息取出來並執行呢?這就要靠 Looper 了。

Looper

創建 Handler 之前必須先創建 Looper,而主線程已經爲我們自動創建了 Looper,無需再手動創建,見 ActivityThread.javamain() 方法:

public static void main(String[] args) {
...
 Looper.prepareMainLooper(); // 創建主線程 Looper
...
}

prepareMainLooper()

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

sMainLooper 只能被初始化一次,也就是說 prepareMainLooper() 只能調用一次,否則將直接拋出異常。

prepare()

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

private static void prepare(boolean quitAllowed) {
    // 每個線程只能執行一次 prepare(),否則會直接拋出異常
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 將 Looper 存入 ThreadLocal
    sThreadLocal.set(new Looper(quitAllowed));
}

主線程中調用的是 prepare(false),說明主線程 Looper 是不允許退出的。因爲主線程需要源源不斷的處理各種事件,一旦退出,系統也就癱瘓了。而我們在子線程調用 prepare() 來初始化 Looper時,默認調動的是 prepare(true),子線程 Looper 是允許退出的。

每個線程的 Looper 是通過 ThreadLocal 來存儲的,保證其線程私有。

再回到文章開頭介紹的 Handler 的構造函數中 mLooper 變量的初始化:

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

也是通過當前線程的 ThreadLocal 來獲取的。

構造函數

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed); // 創建 MessageQueue
    mThread = Thread.currentThread(); // 當前線程
}

再對照 Handler 的構造函數:

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

其中的關係就很清晰了。

  • Looper 持有 MessageQueue 對象的引用
  • Handler 持有 Looper 對象的引用以及 Looper 對象的 MessageQueue 的引用

loop()

看到這裏,消息隊列還沒有真正的運轉起來。我們先來看一個子線程使用 Handler 的標準寫法:

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.loop()

public static void loop() {
    final Looper me = myLooper(); // 從 ThreadLocal 中獲取當前線程的 Looper
    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;
        }

        ...  // 省略部分代碼
       

        try {
            msg.target.dispatchMessage(msg); // 通過 Handler 分發 Message
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        ...  // 省略部分代碼

        msg.recycleUnchecked(); // 將消息放入消息池,以便重複利用
    }
}

簡單說就是一個死循環不停的從 MessageQueue 中取消息,取到消息就通過 Handler 來進行分發,分發之後回收消息進入消息池,以便重複利用。

從消息隊列中取消息調用的是 MessageQueue.next() 方法,之前已經分析過。在沒有消息的時候可能會阻塞,避免死循環消耗 CPU。

取出消息之後進行分發調用的是 msg.target.dispatchMessage(msg)msg.target 是 Handler 對象,最後再來看看 Handler 是如何分發消息的。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) { // callback 是 Runnable 類型,通過 post 方法發送
        handleCallback(msg);
    } else {
        if (mCallback != null) { // Handler 的 mCallback參數 不爲空時,進入此分支
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg); // Handler 子類實現的  handleMessage 邏輯
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}
  • Message 的 callback 屬性不爲空時,說明消息是通過 postXXX() 發送的,直接執行 Runnable 即可。
  • Handler 的 mCallback 屬性不爲空,說明構造函數中傳入了 Callback 實現,調用 mCallback.handleMessage(msg) 來處理消息
  • 以上條件均不滿足,只可能是 Handler 子類重寫了 handleMessage() 方法。這好像也是我們最常用的一種形式。

Message

之所以把 Message 放在最後說,因爲我覺得對整個消息機制有了一個完整的深入認識之後,再來了解 Message 會更加深刻。首先來看一下它有哪些重要屬性:

int what :消息標識
int arg1 : 可攜帶的 int 值
int arg2 : 可攜帶的 int 值
Object obj : 可攜帶內容
long when : 超時時間
Handler target : 處理消息的 Handler
Runnable callback : 通過 post() 發送的消息會有此參數

Message 有 public 修飾的構造函數,但是一般不建議直接通過構造函數來構建 Message,而是通過 Message.obtain() 來獲取消息。

obtain()

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

sPool 是消息緩存池,鏈表結構,其最大容量 MAX_POOL_SIZE 爲 50。obtain() 方法會直接從消息池中取消息,循環利用,節約資源。當消息池爲空時,再去新建消息。

recycleUnchecked()

還記得 Looper.loop() 方法中最後會調用 msg.recycleUnchecked() 方法嗎?這個方法會回收已經分發處理的消息,並放入緩存池中。

void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

總結

說到這裏,Handler 消息機制就全部分析完了,相信大家也對整個機制瞭然於心了。

  • Handler 被用來發送消息,但並不是真正的自己去發送。它持有 MessageQueue 對象的引用,通過 MessageQueue 來將消息入隊。
  • Handler 也持有 Looper 對象的引用,通過 Looper.loop() 方法讓消息隊列循環起來。
  • Looper 持有 MessageQueue 對象應用,在 loop() 方法中會調用 MessageQueue 的 next() 方法來不停的取消息。
  • loop() 方法中取出來的消息最後還是會調用 Handler 的 dispatchMessage() 方法來進行分發和處理。

最後,關於 Handler 一直有一個很有意思的面試題:

Looper.loop() 是死循環爲什麼不會卡死主線程 ?

看起來問的好像有點道理,實則不然。你仔細思考一下,loop() 方法的死循環和卡死主線程有任何直接關聯嗎?其實並沒有。

回想一下我們經常在測試代碼時候寫的 main() 函數:

public static void main(){
    System.out.println("Hello World");
}

姑且就把這裏當做主線程,它裏面沒有死循環,執行完就直接結束了,沒有任何卡頓。但是問題是它就直接結束了啊。在一個 Android 應用的主線程上,你希望它直接就結束了嗎?那肯定是不行的。所以這個死循環是必要的,保證程序可以一直運行下去。Android 是基於事件體系的,包括最基本的 Activity 的生命週期都是由事件觸發的。主線程 Handler 必須保持永遠可以相應消息和事件,程序才能正常運行。

另一方面,這並不是一個時時刻刻都在循環的死循環,當沒有消息的時候,loop() 方法阻塞,並不會消耗大量 CPU 資源。

關於 Handler 就說到這裏了。還記得文章說過線程的 Looper 對象是保存在 ThreadLocal 中的嗎?下一篇文章就來說說 ThreadLocal 是如何保存 線程局部變量 的。

文章首發微信公衆號: 秉心說 , 專注 Java 、 Android 原創知識分享,LeetCode 題解。

更多最新原創文章,掃碼關注我吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章