找到一篇關(guān)于“MessageQueue”的好文章【轉(zhuǎn)】

https://www.sohu.com/a/145311556_675634 原文地址孕惜。
幫我解釋了Looper.loop()死循環(huán)的問題晨炕。

Android 中有兩個非常重要的知識點瓮栗,分別是Binder機制和Handler機制费奸。前者用于跨進程通訊,并且通過 ServiceManager 給上層應(yīng)用提供了大量的服務(wù)秆撮,而后者用于進程內(nèi)部通訊职辨,以消息隊列的形式驅(qū)動應(yīng)用的運行舒裤。之前的文章已經(jīng)多次分析了Binder相關(guān)的內(nèi)容腾供,復雜程度遠高于Handler鲜滩,之后還會繼續(xù)分析Binder徙硅。說到Handler嗓蘑,做安卓開發(fā)的一定都不會陌生匿乃,一般用于切換線程幢炸。其涉及到的類還有Looper宛徊,MessageQueue岩调,Message 等。其中MessageQueue是事件驅(qū)動的基礎(chǔ)缰揪,本文會重點分析MessageQueue钝腺,其他內(nèi)容會簡單帶過,可以參考生產(chǎn)者-消費者模式定硝。

從Handler的入口開始分析:

Looper.prepare();

1.創(chuàng)建一個Looper蔬啡,并且是線程私有的:sThreadLocal.set(new Looper(quitAllowed));

2.初始化Handler:mHandler = new Handler();箱蟆,在構(gòu)造函數(shù)中會獲取線程私有的Looper空猜,如獲取不到會報錯辈毯。

3.開啟無限循環(huán):Looper.loop();搜贤。

在loop方法中主要代碼如下:

image
  1. 從MessageQueue中獲取待處理的Message(阻塞線程)

  2. 交給與之關(guān)聯(lián)的Handler處理

  3. 回收Message管毙,供Message.obtain()復用

其中msg中的target是在Handler發(fā)送消息的時候賦值的:

image

發(fā)送的消息最終入隊列到了MessageQueue。

簡單總結(jié)一下Handler消息機制的工作原理:

  1. 創(chuàng)建與線程綁定的Looper啃炸,同時會創(chuàng)建一個與之關(guān)聯(lián)的MessageQueue用于存放消息

  2. 開啟消息循環(huán)南用,從MessageQueue中獲取待處理消息裹虫,若無消息會阻塞線程

  3. 通過Handler發(fā)送消息融击,此時會將Message入隊列到MessageQueue中尊浪,并且喚醒等待的Looper

  4. Looper獲取的消息會投遞給對應(yīng)的Handler處理

可以看到其中與MessageQueue相關(guān)的也就兩個操作拇涤,一個是入隊列(MessageQueue是鏈表結(jié)構(gòu)),一個是出隊列券躁,這正是本文介紹的重點也拜。

MessageQueue的創(chuàng)建:

image

nativeInit()方法實現(xiàn)為android_os_MessageQueue_nativeInit():

[android_os_MessageQueue.cpp]

image

這里會創(chuàng)建一個native層的MessageQueue搪泳,并且將引用地址返回給Java層保存在mPtr變量中岸军,通過這種方式將Java層的對象與Native層的對象關(guān)聯(lián)在了一起艰赞。這種在Java層保存Native層對象引用地址來實現(xiàn)關(guān)聯(lián)的方式方妖,在Android源代碼中會經(jīng)车趁伲看到。

然后看一下Native層MessageQueue的構(gòu)造方法:

image

也創(chuàng)建了一個Looper镐牺,并且也是與線程綁定的魁莉,事實上這個Looper與Java層的Looper并沒有多大關(guān)系,一個是處理Native層時間的畦浓,一個是處理Java層事件的讶请。

Java層的Looper會通過調(diào)用MessageQueue的next方法獲取下一個消息屎媳,先看主要部分剿牺,后面省略了一部分IdleHandler的處理邏輯环壤,用于空閑的時候處理不緊急事件用的郑现,有興趣的自行分析:

image

這里有必要提一下MessageQueue的數(shù)據(jù)結(jié)構(gòu)接箫,是一個單向鏈表辛友,Message對象有個next字段保存列表中的下一個,MessageQueue中的mMessages保存鏈表的第一個元素邓梅。

循環(huán)體內(nèi)首先調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis)日缨,這是一個native方法掖看,實際作用就是通過Native層的MessageQueue阻塞nextPollTimeoutMillis毫秒的時間面哥。

1.如果nextPollTimeoutMillis=-1尚卫,一直阻塞不會超時焕毫。

2.如果nextPollTimeoutMillis=0驶乾,不會阻塞级乐,立即返回风科。

3.如果nextPollTimeoutMillis>0,最長阻塞nextPollTimeoutMillis毫秒(超時)题山,如果期間有程序喚醒會立即返回顶瞳。

暫時知道這些就可以繼續(xù)向下分析了愕秫,native方法后面會講到戴甩。

如果msg.target為null,則找出第一個異步消息协饲,什么時候msg.target是null呢囱稽?看下面代碼:

image

這個方法直接在MessageQueue中插入了一個Message战惊,并且未設(shè)置target吞获。它的作用是插入一個消息屏障,這個屏障之后的所有同步消息都不會被執(zhí)行刁绒,即使時間已經(jīng)到了也不會執(zhí)行烤黍。

可以通過public void removeSyncBarrier(int token)來移除這個屏障,參數(shù)是post方法的返回值嫂丙。

這些方法是隱藏的或者是私有的跟啤,具體應(yīng)用場景可以查看ViewRootImpl中的void scheduleTraversals()方法隅肥,它在繪圖之前會插入一個消息屏障袄简,繪制之后移除绿语。

回到之前的next方法汞舱,如果發(fā)現(xiàn)了一個消息屏障宗雇,會循環(huán)找出第一個異步消息(如果有異步消息的話)赔蒲,所有同步消息都將忽略(平常發(fā)送的一般都是同步消息)舞虱,可以通過setAsynchronous(boolean async)設(shè)置為異步消息。

繼續(xù)往下损趋,如果有消息需要處理浑槽,先判斷時間有沒有到,如果沒到的話設(shè)置一下阻塞時間nextPollTimeoutMillis篙挽,進入下次循環(huán)的時候會調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis);阻塞铣卡;

否則把消息返回給調(diào)用者煮落,并且設(shè)置mBlocked = false代表目前沒有阻塞苫耸。

如果阻塞了有兩種方式喚醒褪子,一種是超時了嫌褪,一種是被主動喚醒了。根據(jù)生產(chǎn)消費模式裙秋,生產(chǎn)者有產(chǎn)品的時候一般情況下會喚醒消費者缨伊。那么MessageQueue入隊列的時候應(yīng)該會去喚醒刻坊,下面看一下MessageQueue入隊列的方法,截取了主要邏輯:

image

上面的代碼主要就是加入鏈表的時候按時間順序從小到大排序谭胚,然后判斷是否需要喚醒,如果需要喚醒則調(diào)用nativeWake(mPtr);來喚醒之前等待的線程胡控。

再總結(jié)一下MessageQueue獲取消息和加入消息的邏輯:

獲取消息:

1.首次進入循環(huán)nextPollTimeoutMillis=0昼激,阻塞方法nativePollOnce(ptr, nextPollTimeoutMillis)會立即返回

2.讀取列表中的消息,如果發(fā)現(xiàn)消息屏障,則跳過后面的同步消息橙困,總之會通過當前時間敛劝,是否遇到屏障來返回符合條件的待處理消息

3.如果沒有符合條件的消息,會處理一些不緊急的任務(wù)(IdleHandler)纷宇,再次進入第一步

加入消息:

1.加入消息比較簡單夸盟,按時間順序插入到消息鏈表中,如果是第一個那么根據(jù)mBlocked判斷是否需要喚醒線程像捶,如果不是第一個一般情況下不需要喚醒(如果加入的消息是異步的需要另外判斷)

到這里其實關(guān)于MessageQueue已經(jīng)分析的差不多了上陕,其中有兩個native方法沒有涉及到分別是nativePollOnce,nativeWake拓春,其實之前結(jié)論已經(jīng)給出了释簿,兩個方法都會傳入mPtr,在native層對應(yīng)的是NativeMessageQueue的引用地址。

感興趣的可以繼續(xù)往下看,先看一下nativePollOnce的實現(xiàn):

[android_os_MessageQueue.cpp]

image

通過傳進來的ptr獲取NativeMessageQueue對象的指針,然后調(diào)用NativeMessageQueue對象的pollOnce方法:

[android_os_MessageQueue.cpp]

image

調(diào)用的是Looper的pollOnce方法,這個Native層的Looper是在初始化NativeMessageQueue的時候創(chuàng)建的柳譬。

[Looper.cpp]

image

先是處理native層的Response人柿,這個直接跳過江咳,最終調(diào)用pollInner

image
image

這個方法有點長,首先會根據(jù)Native Message的信息計算此次需要等待的時間,再調(diào)用

image

來等待事件發(fā)生,其中是epoll是Linux下多路復用IO接口select/poll的增強版本,具體可以自行查閱相關(guān)文章情臭,查考:Linux IO模式及 select、poll、epoll詳解

如果epoll_wait返回了,那么可能是出錯返回揪荣,可能是超時返回,可能是有事件返回挨决,如果是前兩種情況跳轉(zhuǎn)到Done處刷晋。

如果有事件發(fā)生,會判斷事件是否是mWakeEventFd(喚醒的時候?qū)懭氲奈募┳霾煌幚怼T贒one處會處理Native事件润梯,還有Response矫渔。

總結(jié)一下就是,Java層的阻塞是通過native層的epoll監(jiān)聽文件描述符的寫入事件來實現(xiàn)的蚁袭。

最后看一下nativeWake:

image

和之前一樣删性,也是通過long類型的ptr獲取NativeMessageQueue對象的指針它掂,再調(diào)用wake方法:

image

調(diào)用的也是Looper的方法:

image

重點是write(mWakeEventFd, &inc, sizeof(uint64_t)),寫入了一個1用押,這個時候epoll就能監(jiān)聽到事件,也就被喚醒了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末笛辟,一起剝皮案震驚了整個濱河市手幢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌监透,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阅虫,死亡現(xiàn)場離奇詭異购城,居然都是意外死亡侮攀,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長层亿,這世上最難降的妖魔是什么迎吵? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好缘眶,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布蒋譬。 她就那樣靜靜地躺著剂买,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播虎敦,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼摩骨,長吁一口氣:“原來是場噩夢啊……” “哼茎用!你這毒婦竟也來了古涧?” 一聲冷哼從身側(cè)響起柒昏,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤淳梦,失蹤者是張志新(化名)和其女友劉穎弦疮,沒想到半個月后啸罢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡柄延,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年僵刮,在試婚紗的時候發(fā)現(xiàn)自己被綠了汉规。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡常空,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情病曾,我是刑警寧澤从绘,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布朋蔫,位于F島的核電站青扔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜豹休,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一卷要、第九天 我趴在偏房一處隱蔽的房頂上張望瓶堕。 院中可真熱鬧激捏,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柱锹。三九已至,卻和暖如春有巧,著一層夾襖步出監(jiān)牢的瞬間享言,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留沈条,地道東北人汗洒。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蒸矛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

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

  • 1. 前言 在之前的圖解Handler原理最后留下了幾個課后題癞揉,如果還沒看過那篇文章的烦味,建議先看那篇文章,課后題如...
    唐江旭閱讀 5,949評論 5 45
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,293評論 25 707
  • Android Handler機制系列文章整體內(nèi)容如下: Android Handler機制1之ThreadAnd...
    隔壁老李頭閱讀 7,952評論 4 26
  • 【Android Handler 消息機制】 前言 在Android開發(fā)中森逮,我們都知道不能在主線程中執(zhí)行耗時的任務(wù)...
    Rtia閱讀 4,849評論 1 28
  • 我可能是幸運的。我知道滿意的愛情并不很多,需要種種機遇。我只是想哩照,不應(yīng)該因為現(xiàn)實的不滿意就遷怒于那夢想,說它本來沒...
    你好阿七閱讀 398評論 0 2