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方法中主要代碼如下:
從MessageQueue中獲取待處理的Message(阻塞線程)
交給與之關(guān)聯(lián)的Handler處理
回收Message管毙,供Message.obtain()復用
其中msg中的target是在Handler發(fā)送消息的時候賦值的:
發(fā)送的消息最終入隊列到了MessageQueue。
簡單總結(jié)一下Handler消息機制的工作原理:
創(chuàng)建與線程綁定的Looper啃炸,同時會創(chuàng)建一個與之關(guān)聯(lián)的MessageQueue用于存放消息
開啟消息循環(huán)南用,從MessageQueue中獲取待處理消息裹虫,若無消息會阻塞線程
通過Handler發(fā)送消息融击,此時會將Message入隊列到MessageQueue中尊浪,并且喚醒等待的Looper
Looper獲取的消息會投遞給對應(yīng)的Handler處理
可以看到其中與MessageQueue相關(guān)的也就兩個操作拇涤,一個是入隊列(MessageQueue是鏈表結(jié)構(gòu)),一個是出隊列券躁,這正是本文介紹的重點也拜。
MessageQueue的創(chuàng)建:
nativeInit()方法實現(xiàn)為android_os_MessageQueue_nativeInit():
[android_os_MessageQueue.cpp]
這里會創(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)造方法:
也創(chuàng)建了一個Looper镐牺,并且也是與線程綁定的魁莉,事實上這個Looper與Java層的Looper并沒有多大關(guān)系,一個是處理Native層時間的畦浓,一個是處理Java層事件的讶请。
Java層的Looper會通過調(diào)用MessageQueue的next方法獲取下一個消息屎媳,先看主要部分剿牺,后面省略了一部分IdleHandler的處理邏輯环壤,用于空閑的時候處理不緊急事件用的郑现,有興趣的自行分析:
這里有必要提一下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呢囱稽?看下面代碼:
這個方法直接在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入隊列的方法,截取了主要邏輯:
上面的代碼主要就是加入鏈表的時候按時間順序從小到大排序谭胚,然后判斷是否需要喚醒,如果需要喚醒則調(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]
通過傳進來的ptr獲取NativeMessageQueue對象的指針,然后調(diào)用NativeMessageQueue對象的pollOnce方法:
[android_os_MessageQueue.cpp]
調(diào)用的是Looper的pollOnce方法,這個Native層的Looper是在初始化NativeMessageQueue的時候創(chuàng)建的柳譬。
[Looper.cpp]
先是處理native層的Response人柿,這個直接跳過江咳,最終調(diào)用pollInner
這個方法有點長,首先會根據(jù)Native Message的信息計算此次需要等待的時間,再調(diào)用
來等待事件發(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:
和之前一樣删性,也是通過long類型的ptr獲取NativeMessageQueue對象的指針它掂,再調(diào)用wake方法:
調(diào)用的也是Looper的方法:
重點是write(mWakeEventFd, &inc, sizeof(uint64_t)),寫入了一個1用押,這個時候epoll就能監(jiān)聽到事件,也就被喚醒了。