一個小型合做的流水線——Android Handler

當咱們遇到多線程的問題,考慮到線程間消息傳遞的時候,首先想到的確定是 Handler。雖然寫這篇文章的初衷並非想探究 Handler 的機制,但咱們仍是先從這個被說爛了的話題開始。java

Handler 的工做原理

首先,在瞭解 Handler 以前,咱們須要瞭解有四個關鍵的類是組成 Handler 的基礎。它們分別是面試

  • Handler 負責協調安排將來某個時間點的消息或可運行狀態,以及對不一樣線程的運行機制進行合理的排隊
  • Looper 主要做用如其名,一個循環的機制,爲線程運行消息循環分發
  • MessageQueue 一個鏈式隊列數據結構,將消息實體串聯成鏈
  • Message 消息實體,存儲咱們須要傳遞的消息的內容和信息等

Looper 和 MessageQueue——Handler 的流水線

ActivityThread 類中,做爲入口方法的 main() 方法中,經過調用 Looperloop() 方法,啓動 Looper 的循環機制(這裏咱們注意到,在方法的最後,拋出了一個主線程循環意外退出的異常,說明 Android 的主流程都是經過 Handler 來驅動的)。markdown

/** * ActivityThread */
public static void main(String[] args) {
    
    // ...
    Looper.prepareMainLooper();
	// ...
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製代碼

進入 loop() 方法,這裏咱們能夠看到一個死循環,傳說中的死循環這麼快就跟咱們見面了嗎?其實否則,咱們平時面試時更關注的死循環並非這個,或者說它只是其中的一部分。廢話先不說,這段代碼精簡後的大體做用能夠概括爲:從 MessageQueue 的對象隊列裏取出一個未處理的消息,即 Message 實例,而後獲取 Message 對象的 target 屬性,它是一個 Handler 對象,而後經過 dispatchMessage() 方法來將消息進行分發。數據結構

/** * Looper */
public static void loop() {
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    // 我最開始讀到這段源碼的時候,很困惑這個方法爲何調用了兩遍,後來通過思索想明白了緣由,這裏稍做記錄。
    // 這個方法調用的是 native 的代碼,源碼以下:
    // int64_t IPCThreadState::clearCallingIdentity()
	// {
    // int64_t token = ((int64_t)mCallingUid<<32) | mCallingPid;
    // clearCaller();
    // return token;
	// }
	// void IPCThreadState::clearCaller()
	// {
	// mCallingPid = getpid(); //當前進程pid賦值給mCallingPid
	// mCallingUid = getuid(); //當前進程uid賦值給mCallingUid
	// }
    // 具體做用能夠網上自行搜索,這個方法的做用,簡而言之,就是將(多是)其餘進程的 pid 和 uid 清除,更換爲本身的,
    // 而 token 是用來存儲原來進程的 pid 和 uid 的64位整型,因此第一遍調用時返回的是以前進程的 pid 和 uid 信息,
    // 再次調用時,返回的纔是當前進程的,而被我精簡掉的源碼裏須要經過這個 token 來判斷進程是否切換過,因此這個方法在這裏會調用兩遍
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    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);
            
        } catch (Exception exception) {
            throw exception;
        }

        msg.recycleUnchecked();
    }
}
複製代碼

由於 dispatchMessage() 方法比較簡單,因此咱們先越過過程看結果,看看這個方法的實現。多線程

/** * Handler */
public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
複製代碼

這裏就直接調用了 Handler 對象的 handleMessage() 方法,並傳遞 Message 的實例,因此咱們在使用 Handler 時在這個方法中就能夠接收到咱們須要的消息實體(callback 默認不實現,實現後變動爲調用相應的方法)。app

好,結果咱們已經知道了,那如今咱們回過頭來,研究一下上面 Looper 類的 loop() 方法中調用的 queue.next() 方法是如何拿到消息實體的(後面的註釋已經提醒咱們這個方法可能會阻塞)。less

/** * MessageQueue */
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
    // 這個變量做爲 nativePollOnce 方法的參數表示休眠的時間
    // 當值爲 -1 時,表示無限休眠,直到有線程喚醒
    // 當值爲 0 時,表示當即喚醒
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
		
        // 根據 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;
            // 若是 Message 的 target 爲 null,則說明它是 Looper synchronization barrier 的臨界點
            if (msg != null && msg.target == null) {
                // Stalled by a barrier. Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;and the message is the earliest asynchronous message in the queue
                } while (msg != null && !msg.isAsynchronous());
            }
            // 通過上面的循環後,到達這裏的 Message 要麼是 null,要麼是 isAsynchronous() 方法返回 true
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready. Set a timeout to wake up when it is ready.
                    // 消息的發送時間未到,此時的 nextPollTimeoutMillis 爲距離 msg 的發送時間的時間間隔,
                    // 那 nativePollOnce() 方法休眠相應的時間後,msg 即到了它該發送的時間
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a 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();
                    return msg;
                }
            } else {
                // No more messages.
                // 沒有更多的消息,此時 nextPollTimeoutMillis 賦值爲 -1,
                // 那 nativePollOnce() 方法將致使線程永久休眠,直到有其餘線程將其喚醒
                nextPollTimeoutMillis = -1;
            }

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

next() 方法看起來很長,可是它的主要工做只有一件事,就是找到符合要求的 Message 實例並返回。可是這個方法又特別重要,有一個常問的重要的面試考點。咱們上面已經提到了,Looperloop() 方法中有一個死循環,做用是源源不斷地從 MessageQueue 中「打撈」 Message 實體,而「打撈」的動做正是經過 next() 方法完成的。在 next() 方法中,也有一個死循環,完成上面的「打撈」工做。具體的細節我在代碼中做了部分註釋,能夠幫助理解。其中提到了一個概念——「Looper synchronization barrier」,關於它的介紹咱們放在下面的內容裏。async

好了,介紹完了 Handler 機制中的死循環,它是死循環雙重嵌套的形式,那麼面試問題來了:請問 Handler 機制中的死循環是如何作到不阻塞主線程的呢?網上搜索到的答案一般是死循環也未必會阻塞主線程,只要不在 onCreate()onStart() 等生命週期中阻塞就不會致使界面的卡死,其次在 MessageQueue 中沒有 Message 實體時,線程會進入到一個休眠的狀態,在有新消息來臨時線程纔會被喚醒,balabala小魔仙……咱們看到 next() 方法的死循環的一開始有一句代碼 nativePollOnce(),它是一個 native 的方法,經過執行 Linux 中的 epoll 機制來是線程休眠和運行,它和 nativeWake() 方法配對使用,在類文件的開頭均有聲明。因此每次在執行完一遍 next() 方法後,都會根據 nextPollTimeoutMillis 變量的值來決定休眠的時間。若是沒有可被「打撈」的消息,那麼線程將被永久休眠,等待被喚醒。那麼在哪裏喚醒的呢,咱們暫時無論,在這裏先記住線程休眠,主線程被阻塞,等待一個白馬王子將其喚醒。至於白馬王子什麼時候到來,咱們靜待。ide

Handler——消息操做臺

如今,咱們再從消息發送的源頭追溯——經過 Handler 的一系列 sendMessage() 方法,將消息發送出去。oop

咱們以 sendEmptyMessage() 方法爲例,通過一系列的調用後,最終會執行 enqueueMessage() 方法,該方法又會調用 MessageQueueenqueueMessage() 方法,該方法代碼以下:

/** * MessageQueue */
boolean enqueueMessage(Message msg, long when) {

    synchronized (this) {

        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;
}
複製代碼

好耶,「白馬王子」來了!看到了吧,nativeWake() 方法顯真身了,當有新的消息壓入隊列,消息須要被處理,此時就須要喚醒睡眠的線程。可是「白馬王子」

的到來是須要條件的,即 needWake,那究竟是怎樣的條件呢?想一想無非是判斷當前的線程是否處於可能阻塞的狀態,咱們來看看。

在第一個條件 p == null || when == 0 || when < p.when 下,相比於羅列全部的知足條件的狀況,更簡單的方法是判斷咱們前面的線程被阻塞的狀況是否是在這裏被斷定爲 needWake, 由於在等待新的消息,因此 mMessage 值爲 null,此時的 needWake = mBlocked,而 mBlocked 的線程被阻塞的狀況下值是爲 true 的,因此這裏會被斷定爲須要被喚醒。而在 else 分支中,其實條件爲p != null && when != 0 && when >= p.when,這說明消息隊列中的消息並無被取完,而是正在一個循環中,一般狀況下是不須要再喚醒它,除非像註釋中所說的 there is a barrier at the head of the queue and the message is the earliest asynchronous message in the queue

到這裏,Handler 的大概工做流程就能夠串聯起來了——循環隊列至關於物流,消息至關於商品,物流無時無刻在運轉,當你須要新的商品時,商品被商家發送至物流,而後分發到目標客戶即你的手中。

Looper synchronization barrier

在看源碼的時候,不止一次會接觸到這個概念,並且在上面咱們也已經率先使用了這個概念,那麼這個概念究竟是個什麼?搞清楚這個問題,咱們須要從它的特徵入手,在 MessageQueuenext() 方法中,咱們說若是 mMessages.target == null,那麼它就是一個 barrier 的臨界點,咱們經過查找 mMessage 的寫引用,最終定位到 MessageQueue#postSyncBarrier() 這個方法。我這裏摘錄它的註釋,相信你們對這個概念就會有一個清晰的認識。

Posts a synchronization barrier to the Looper's message queue.

Message processing occurs as usual until the message queue encounters the synchronization barrier that has been posted. When the barrier is encountered, later synchronous messages in the queue are stalled (prevented from being executed) until the barrier is released by calling {@link #removeSyncBarrier} and specifying the token that identifies the synchronization barrier.

This method is used to immediately postpone execution of all subsequently posted synchronous messages until a condition is met that releases the barrier. Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier and continue to be processed as usual.

This call must be always matched by a call to {@link #removeSyncBarrier} with the same token to ensure that the message queue resumes normal operation. Otherwise the application will probably hang!

在瞭解這個概念以前還須要知道一個屬性的存在,那就是 Message#isAsynchronous()

好了,總結一下就是 Looper synchronization barrierMessageQueue 中那些 target == nullMessage,它們不須要被髮送,只做爲一種隊列狀態的判斷標識。當 Message.isAsynchronous() == true 時,遇到 Looper synchronization barrier 時,Looper 會被阻塞,直到 removeSyncBarrier() 方法(和 postSyncBarrier() 方法成對使用)移除這個標識。可是若是 Message.isAsynchronous() == false 時,則不會被 barrier 阻斷,具體使用場景見上方註釋。

太多的代碼和解說趕不上一張圖片更能讓人造成概念,那我從網上找了一張圖片稍做加工,但願能夠比較形象地說明 Handler 機制中各個類之間的分工。

映射關係

爲了話題的天然過渡,這裏咱們思考一個問題,一個線程能夠有多個 Looper 嗎?一個 Looper 能夠對應多個 MessageQueue 嗎?從源碼中看,一個線程是沒法建立多個 Looper 和多個 MessageQueue 的,那麼多個 LooperMessageQueue 會致使什麼問題呢?最主要的就是咱們上面說的消息同步性的問題了,多個消息隊列和循環體如何保證消息的次序限制以及同步分發就是一個很複雜的問題。那麼系統又是如何保證每一個線程的 Looper 的惟一性的呢?那就是使用 ThreadLocal 了。

ThreadLocal

因爲本篇內容旨在討論 Handler 的相關機制,因此對於 ThreadLocal 的機制不作過多討論。

Looper#prepare() 方法在 Looper 使用前必須調用,在這個方法裏能夠看到 ThreadLocal 的應用。

/** * Looper */
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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));
}
複製代碼

sThreadLocal 對象是一個全局的靜態對象,經過使用 sThreadLocal#set() 方法來存儲 Looper 的實例,而 ThreadLocal 把真正的對象存儲交給了它的靜態內部類 ThreadLocalMap,這是一個自定義的 hash map,具體內部實現請自行閱讀源碼。

/** * ThreadLocal */

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
複製代碼

能夠看到,ThreadLocalMap 又和 Thread 綁定,每一個 Thread 對應一個惟一的 ThreadLocalMap 實例, ThreadLocalMapkey 的類型是 ThreadLocal,而在 Looper 中的 sThreadLocal 做爲靜態對象,進程內惟一,經過這樣的關係,能夠惟一對應到 TreadLocalMap 中的某個元素,實現讀取。

碎碎念

前面兩個月經歷找工做和工做後的一堆雜事,致使好久沒有更新。這篇也是匆忙趕工,邏輯上和圖文代碼編排上都有一些問題,還請多多包涵。以前作的是 Flutter 的工做,如今又回到了 Android,Flutter 的內容也會繼續帶着更新,後面我會盡可能保持正常的更新頻率,可是水平確實有限,最後仍是請你們雅正和包涵。

相關文章
相關標籤/搜索