Handler 源碼解析:nativePollOnce阻塞和nativeWake喚醒

收錄:

Android Handler機制 - MessageQueue如何處理消息
Handler 如何做到阻塞
Android篇:2019初中級Android開發社招面試解答(中)

Handler消息機制組成:

  • Message(消息):需要被傳遞的消息,消息分為硬件產生的消息(如按鈕、觸摸)和軟件生成的消息。
  • MessageQueue(消息隊列):負責消息的存儲與管理,負責管理由 Handler發送過來的Message。讀取后會自動刪除消息,單鏈表維護,插入和刪除上有優勢。在其next()方法中會無限循環,不斷判斷是否有消息,有就返回這條消息并移除。
  • Handler(消息處理器):負責Message的發送及處理。主要向消息池發送各種消息事件(Handler.sendMessage())和處理相應消息事件(Handler.handleMessage()),按照先進先出執行,內部使用的是單鏈表的結構。
  • Looper(消息池/循環機制):負責關聯線程以及消息的分發,在該線程下從 MessageQueue獲取 Message,分發給Handler,Looper創建的時候會創建一個 MessageQueue,調用loop()方法的時候消息循環開始,其中會不斷調用messageQueue的next()方法,當有消息就處理,否則阻塞在messageQueue的next()方法中。當Looper的quit()被調用的時候會調用messageQueue的quit(),此時next()會返回null,然后loop()方法也就跟著退出。

如何保證looper的唯一性

每個線程只有一個looper,而每個Thread中都又一個關鍵Threadlocal。是用于存放每個線程的looper對象的,存取的方式是通過get/set。相當于一個map的存放方式。鍵位key是當前線程的實例。value就是looper對象。所以每次創建looper都會去ThreadLocal里面找有沒有當前線程的looper。

如何知道 message 發送到哪個handler處理
當使用 Handler.sendMessage() 發送消息時,調用 enqueueMessage 方法內有 msg.target = this 將 Handler 實例賦值給 msg 對象。當 loop() 取出消息時,調用 dispatchMessage 方法根據 target 屬性,回調對應 handler 實例的 handlerMessage 方法處理消息。

具體流程圖:
Handler工作流程
  • App啟動時創建一個主線程(UI),接著UI線程會創建一個Looper,同時也會在在Looper內部創建一個消息隊列。而在創鍵Handler的時候取出當前線程的Looper,并通過該Looper對象獲得消息隊列(MessageQueue),然后Handler在子線程中通過MessageQueue.enqueueMessage在消息隊列中添加一條Message。
  • 通過Looper.loop() 開啟消息循環不斷輪詢調用 MessageQueue.next(),取得對應的Message并且通過Handler.dispatchMessage傳遞給Handler,最終調用Handler.handlerMessage處理消息。

源碼分析:

  1. 插入消息
    MessageQueue.enqueueMessage:Handler調用sendMessage()發送消息,而Handler內部通過Looper對象得到MessageQueue對象后又調用MessageQueue.enqueueMessage方法。
boolean enqueueMessage(Message msg, long when) {
            ...

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //插入前先消息隊列是否有消息,新的頭,如果被阻止,則喚醒事件隊列。
            if (p == null || when == 0 || when < p.when) {
                //將消息放進隊列頭部
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;//指示next()是否被阻止在pollOnce()中以非零超時等待。如果阻塞,則需要喚醒looper
            } 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.
                /*插入隊列中間。 通常,除非隊列的開頭有障礙并且消息是隊列中最早的
                  異步消息,否則我們不必喚醒事件隊列。(隊列中消息不為空,并且next()也沒有阻塞)*/
                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;
            }

            // 如果looper阻塞/休眠中,則喚醒looper循環機制處理消息
            if (needWake) {
                nativeWake(mPtr);//喚醒
            }
        }
        return true;
    }

調用nativeWake喚醒(這部分內容出自頭部連接,詳細源碼分析可看前輩的)

//android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr){
  NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
  nativeMessageQueue->wake();
}

void NativeMessageQueue::wake(){
    mLooper->wake();
}

void Looper::wake(){
    ...
    //往mWakeEventFd 中write 1,用以喚醒 looper
    ssize_t mWrite = TEMP_FAILURE_READY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
}

既然有寫入消息,那必定要把消息處理掉,所以喚醒了epoll_wait(),然后繼續方法調動awoken(),這個方法就是將之前寫入的1讀出,表示消費這個事件

void Looper::awaken(){
    ...
    //讀取頭部消息,靜默處理掉
    TEMP_FAILURE_READY(read(mWakeEventFd, &counter, sizeof(uint64_t)));
}

隨后在Java 層的next()@MessageQueue 就被喚醒,讀取在enqueueMessage()@MessageQueue 插在隊頭的消息進行處理

  1. Looper循環讀取消息
    當looper循環機制在MessageQueue的next()讀取消息時發現消息隊列中沒有消息時,就會調用nativePollOnce(ptr, nextPollTimeoutMillis);將next()阻塞在PollOnce中。looper也就進入了休眠期。
@UnsupportedAppUsage
    Message next() {
        // 如果消息循環已經退出并被處理,請返回此處。
        // 如果應用程序嘗試退出后不支持的循環程序,則會發生這種情況。
        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();
            }
            //就是在這里根據nextPollTimeoutMillis判斷是否要阻塞
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // 嘗試檢索下一條消息。 如果找到則返回。
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // 被障礙擋住了。 在隊列中查找下一條異步消息。
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {//隊列中拿到的消息不為null
                    if (now < msg.when) {
                        // 下一條消息尚未準備好。 設置超時以使其在準備就緒時醒來。
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 正常返回處理
                        ...
                } else {
                    // 隊列中沒有消息,標記阻塞looper循環進入休眠
                    nextPollTimeoutMillis = -1;
                }

                // 現在已處理所有掛起的消息,處理退出消息。
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // 空閑句柄僅在隊列為空或將來要處理隊列中的第一條消息(可能是屏障)時才運行。
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                ...
            }

            ...

            // 將空閑處理程序計數重置為0,這樣我們就不會再次運行它們。
            pendingIdleHandlerCount = 0;

            // 在調用空閑處理程序時,可能已經傳遞了一條新消息,
            //因此返回并再次查找未處理消息,而無需等待。
            nextPollTimeoutMillis = 0;
        }
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容