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

收錄:

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

Handler消息機(jī)制組成:

  • Message(消息):需要被傳遞的消息,消息分為硬件產(chǎn)生的消息(如按鈕进胯、觸摸)和軟件生成的消息。
  • MessageQueue(消息隊列):負(fù)責(zé)消息的存儲與管理胁镐,負(fù)責(zé)管理由 Handler發(fā)送過來的Message。讀取后會自動刪除消息盯漂,單鏈表維護(hù),插入和刪除上有優(yōu)勢就缆。在其next()方法中會無限循環(huán),不斷判斷是否有消息竭宰,有就返回這條消息并移除。
  • Handler(消息處理器):負(fù)責(zé)Message的發(fā)送及處理切揭。主要向消息池發(fā)送各種消息事件(Handler.sendMessage())和處理相應(yīng)消息事件(Handler.handleMessage()),按照先進(jìn)先出執(zhí)行伴箩,內(nèi)部使用的是單鏈表的結(jié)構(gòu)。
  • Looper(消息池/循環(huán)機(jī)制):負(fù)責(zé)關(guān)聯(lián)線程以及消息的分發(fā)嗤谚,在該線程下從 MessageQueue獲取 Message,分發(fā)給Handler巩步,Looper創(chuàng)建的時候會創(chuàng)建一個 MessageQueue,調(diào)用loop()方法的時候消息循環(huán)開始椅野,其中會不斷調(diào)用messageQueue的next()方法,當(dāng)有消息就處理竟闪,否則阻塞在messageQueue的next()方法中离福。當(dāng)Looper的quit()被調(diào)用的時候會調(diào)用messageQueue的quit()炼蛤,此時next()會返回null,然后loop()方法也就跟著退出理朋。

如何保證looper的唯一性

每個線程只有一個looper绿聘,而每個Thread中都又一個關(guān)鍵Threadlocal。是用于存放每個線程的looper對象的熄攘,存取的方式是通過get/set。相當(dāng)于一個map的存放方式挪圾。鍵位key是當(dāng)前線程的實(shí)例。value就是looper對象洛史。所以每次創(chuàng)建looper都會去ThreadLocal里面找有沒有當(dāng)前線程的looper。

如何知道 message 發(fā)送到哪個handler處理
當(dāng)使用 Handler.sendMessage() 發(fā)送消息時,調(diào)用 enqueueMessage 方法內(nèi)有 msg.target = this 將 Handler 實(shí)例賦值給 msg 對象土思。當(dāng) loop() 取出消息時,調(diào)用 dispatchMessage 方法根據(jù) target 屬性己儒,回調(diào)對應(yīng) handler 實(shí)例的 handlerMessage 方法處理消息。

具體流程圖:
Handler工作流程
  • App啟動時創(chuàng)建一個主線程(UI)闪湾,接著UI線程會創(chuàng)建一個Looper,同時也會在在Looper內(nèi)部創(chuàng)建一個消息隊列途样。而在創(chuàng)鍵Handler的時候取出當(dāng)前線程的Looper,并通過該Looper對象獲得消息隊列(MessageQueue)何暇,然后Handler在子線程中通過MessageQueue.enqueueMessage在消息隊列中添加一條Message。
  • 通過Looper.loop() 開啟消息循環(huán)不斷輪詢調(diào)用 MessageQueue.next()裆站,取得對應(yīng)的Message并且通過Handler.dispatchMessage傳遞給Handler,最終調(diào)用Handler.handlerMessage處理消息宏胯。

源碼分析:

  1. 插入消息
    MessageQueue.enqueueMessage:Handler調(diào)用sendMessage()發(fā)送消息,而Handler內(nèi)部通過Looper對象得到MessageQueue對象后又調(diào)用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) {
                //將消息放進(jìn)隊列頭部
                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循環(huán)機(jī)制處理消息
            if (needWake) {
                nativeWake(mPtr);//喚醒
            }
        }
        return true;
    }

調(diào)用nativeWake喚醒(這部分內(nèi)容出自頭部連接秩霍,詳細(xì)源碼分析可看前輩的)

//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()颠悬,然后繼續(xù)方法調(diào)動awoken(),這個方法就是將之前寫入的1讀出,表示消費(fèi)這個事件

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

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

  1. Looper循環(huán)讀取消息
    當(dāng)looper循環(huán)機(jī)制在MessageQueue的next()讀取消息時發(fā)現(xiàn)消息隊列中沒有消息時届榄,就會調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis);將next()阻塞在PollOnce中。looper也就進(jìn)入了休眠期铝条。
@UnsupportedAppUsage
    Message next() {
        // 如果消息循環(huán)已經(jīng)退出并被處理,請返回此處班缰。
        // 如果應(yīng)用程序嘗試退出后不支持的循環(huán)程序,則會發(fā)生這種情況埠忘。
        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();
            }
            //就是在這里根據(jù)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) {
                        // 下一條消息尚未準(zhǔn)備好。 設(shè)置超時以使其在準(zhǔn)備就緒時醒來鉴腻。
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 正常返回處理
                        ...
                } else {
                    // 隊列中沒有消息,標(biāo)記阻塞looper循環(huán)進(jìn)入休眠
                    nextPollTimeoutMillis = -1;
                }

                // 現(xiàn)在已處理所有掛起的消息爽哎,處理退出消息蜓席。
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // 空閑句柄僅在隊列為空或?qū)硪幚黻犃兄械牡谝粭l消息(可能是屏障)時才運(yùn)行课锌。
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                ...
            }

            ...

            // 將空閑處理程序計數(shù)重置為0,這樣我們就不會再次運(yùn)行它們渺贤。
            pendingIdleHandlerCount = 0;

            // 在調(diào)用空閑處理程序時,可能已經(jīng)傳遞了一條新消息癣亚,
            //因此返回并再次查找未處理消息获印,而無需等待述雾。
            nextPollTimeoutMillis = 0;
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兼丰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鳍征,更是在濱河造成了極大的恐慌,老刑警劉巖艳丛,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異氮双,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)戴差,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人袭厂,你說我怎么就攤上這事∥苹牵” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵爽航,是天一觀的道長。 經(jīng)常有香客問我讥珍,道長,這世上最難降的妖魔是什么衷佃? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮氏义,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘惯悠。我一直安慰自己邻邮,他們只是感情好克婶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著情萤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪筋岛。 梳的紋絲不亂的頭發(fā)上娶视,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天睁宰,我揣著相機(jī)與錄音,去河邊找鬼勋陪。 笑死,一個胖子當(dāng)著我的面吹牛诅愚,可吹牛的內(nèi)容都是我干的劫映。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼泳赋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了祖今?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤千诬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后膏斤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莫辨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了盘榨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡草巡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出型酥,到底是詐尸還是另有隱情,我是刑警寧澤冕末,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布侣颂,位于F島的核電站,受9級特大地震影響憔晒,放射性物質(zhì)發(fā)生泄漏藻肄。R本人自食惡果不足惜拒担,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望从撼。 院中可真熱鬧州弟,春花似錦、人聲如沸婆翔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潭陪。三九已至最蕾,卻和暖如春依溯,著一層夾襖步出監(jiān)牢的瞬間瘟则,已是汗流浹背黎炉。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工壹粟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人趁仙。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像雀费,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子盏袄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容