Read The Fucking Source Code
消息機(jī)制模型:
- Message:消息分為硬件產(chǎn)生的消息(如按鈕左电、觸摸)和軟件生成的消息尤莺;
- MessageQueue:消息隊(duì)列的主要功能向消息池投遞消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next)雁乡;
- Handler:消息輔助類饵史,主要功能向消息池發(fā)送各種消息事件(Handler.sendMessage)和處理相應(yīng)消息事件(Handler.handleMessage)髓抑;
- Looper:不斷循環(huán)執(zhí)行(Looper.loop)袋坑,按分發(fā)機(jī)制將消息分發(fā)給目標(biāo)處理者仗处。
源碼閱讀心得:
消息機(jī)制的分層與解耦:
- 消息機(jī)制分為Java層和Native層,Java端創(chuàng)建了Native端咒彤,Native端與Java端組成了一個(gè)完成的整體疆柔。
- Java層和Native層的有且只有MessageQueue通過JNI建立關(guān)聯(lián),各自實(shí)現(xiàn)自己的邏輯镶柱,功能相似旷档,彼此獨(dú)立。
Java層總結(jié):
- Message的消息池:盡可能用Handler的obtainMessage()方法歇拆,而不是去創(chuàng)建Message鞋屈,因?yàn)檫@樣會(huì)取Message消息池的緩存Message,提高效率故觅。
- MessageQueue:在Java層面厂庇,MessageQueue才是核心,里面包含了很多native方法输吏,它是連接native的唯一入口权旷。
- MessageQueue.enqueueMessage():是將消息按照處理時(shí)序進(jìn)行添加。當(dāng)然添加消息還有postSyncBarrier(這是hide方法)
- Looper.loop():無限循環(huán)(無限循環(huán)的意義在于消息隊(duì)列的信息獲裙峤Α)拄氯,取出MessageQueue的Message躲查。
- MessageQueue.next():無限循環(huán)(無限循環(huán)的意義在于線程等待退出,重新進(jìn)行消息隊(duì)列取值計(jì)算)译柏,next()是MessageQueue的處理核心镣煮,可能會(huì)有阻塞操作,等待nativePollOnce()結(jié)束或者消息隊(duì)列被喚醒鄙麦,也會(huì)處理SyncBarrier邏輯典唇。當(dāng)消息取值失敗,那么會(huì)進(jìn)入Idle狀態(tài)胯府,處理Idle任務(wù)集合(只執(zhí)行一次)介衔,所以queueIdle()的方法還是返回false即可,或者主動(dòng)remove idle也行盟劫。
- 異步消息是為了同步屏障(SyncBarrier)而設(shè)計(jì)的夜牡,如果不設(shè)置SyncBarrier,那么異步消息是沒有作用的侣签,而SyncBarrier是hide方法塘装,只提供給系統(tǒng)來使用,比如UI刷新影所。設(shè)置SyncBarrier(好比給消息設(shè)置優(yōu)先級一樣)蹦肴,首先處理異步消息,然后取消SyncBarrier猴娩,再依次處理消息隊(duì)列阴幌。(系統(tǒng)級別的消息:因?yàn)橄裣到y(tǒng)級別的消息,會(huì)封裝成異步消息卷中,插入異步消息的時(shí)候矛双,會(huì)向隊(duì)頭插入一個(gè)屏障消息,當(dāng)發(fā)現(xiàn)到有這個(gè)屏障消息的時(shí)候蟆豫,就不會(huì)處理后面的普通消息了议忽,就會(huì)在隊(duì)列后面找異步消息,優(yōu)先處理異步消息十减,所以主線程插入異步消息的時(shí)候栈幸,總是會(huì)優(yōu)先處理這個(gè)消息,所以我們主線程所做的一些系統(tǒng)級別的優(yōu)先級比較高的消息帮辟,并不會(huì)阻塞)
- Java層也可以主動(dòng)喚醒線程(當(dāng)然需要調(diào)用Native的方法)速址,可以進(jìn)行喚醒的地方有:MessageQueue的quit()/removeSyncBarrier()/enqueueMessage()
- quit()與quitSafely()的區(qū)別,僅僅在于是否移除當(dāng)前正在處理的消息由驹。移除當(dāng)前正在處理的消息可能會(huì)出現(xiàn)不安全的行為芍锚。
Native層總結(jié):
- Native層提供epoll機(jī)制(是一種 IO 多路復(fù)用機(jī)制,可以同時(shí)監(jiān)控多個(gè)文件描述符,當(dāng)某個(gè)文件描述符就緒并炮,則立刻通知相應(yīng)程序進(jìn)行讀寫操作拿到最新的消息蒿赢,進(jìn)而喚醒等待的線程)來保證消息隊(duì)列處理的低功耗。Java層通過Looper的loop()渣触,在MessageQueue中next()中,最終直通Native層的epoll_wait()方法壹若,線程掛起嗅钻。(就拿主線程來說,如果主線程沒有視圖刷新請求進(jìn)行epoll喚醒店展,那么epoll_wait()會(huì)等到timeout結(jié)束才會(huì)再次喚醒养篓。否則,線程處于空閑等待也就是掛起狀態(tài)赂蕴。所以主線程大多數(shù)的時(shí)候都是出于休眠狀態(tài)柳弄,并不會(huì)消耗大量的 CPU 資源,這就是為什么主線程不會(huì)因?yàn)長oop.loop()里的死循環(huán)卡死)
- Native層處理Native Request和Native Message概说,而Java層處理Java message碧注。通過Native層的Looper.pollInner()方法可以看出,消息處理流程是先處理Native Message糖赔,再處理Native Request萍丐,最后處理Java Message。理解了該流程放典,也就明白有時(shí)上層消息很少逝变,但響應(yīng)時(shí)間卻較長的真正原因。
Handler的通俗解釋
- Android中主線程是不能進(jìn)行耗時(shí)操作的奋构,子線程是不能進(jìn)行更新UI的壳影。所以就有了handler,它的作用就是實(shí)現(xiàn)線程之間的通信弥臼。
- 安卓是由 事件驅(qū)動(dòng) 的宴咧,對事件進(jìn)行處理的就是 looper(每一個(gè)點(diǎn)擊觸摸或者Activity每一個(gè)生命周期),Handler 的創(chuàng)建依賴 該線程 的Looper(Handler利用哪個(gè)線程的Looper創(chuàng)建的實(shí)例醋火,它就和相應(yīng)的線程綁定到一起悠汽,處理該線程上的消息),這就決定了Looper查詢出的msg分發(fā)到Handler的handleMessage()方法是運(yùn)行在 該線程芥驳。
- 主線程Looper從消息隊(duì)列讀取消息柿冲,當(dāng)讀完所有消息時(shí),主線程阻塞兆旬。子線程往消息隊(duì)列發(fā)送消息假抄,并且往管道文件寫數(shù)據(jù),主線程即被喚醒,從管道文件讀取數(shù)據(jù)宿饱,主線程被喚醒只是為了讀取消息熏瞄,當(dāng)消息讀取完畢,再次睡眠谬以。因此loop的循環(huán)并不會(huì)對CPU性能有過多的消耗强饮。
Handler、Thread和HandlerThread的差別
- Handler是線程的消息通訊的橋梁为黎,主要用來發(fā)送消息及處理消息
- Thread普通線程邮丰,如果需要有自己的消息隊(duì)列,需要調(diào)用Looper.prepare()創(chuàng)建Looper實(shí)例铭乾,調(diào)用loop()去循環(huán)消息剪廉。
- HandlerThread是一個(gè)帶有Looper的線程,在HandleThread的run()方法中調(diào)用了Looper.prepare()創(chuàng)建了Looper實(shí)例炕檩,并調(diào)用Looper.loop()開啟了Loop循環(huán)斗蒋,循環(huán)從消息隊(duì)列中獲取消息并交由Handler處理。利用該線程的Looper創(chuàng)建Handler實(shí)例笛质,此Handler的handleMessage()方法是運(yùn)行在子線程中的泉沾。
- HandlerThread適合處理本地IO讀寫操作(讀寫數(shù)據(jù)庫或文件),因?yàn)楸镜豂O操作耗時(shí)不長妇押,對于單線程+異步隊(duì)列不會(huì)產(chǎn)生較大阻塞爆哑,而網(wǎng)絡(luò)操作相對比較耗時(shí),容易阻塞后面的請求舆吮,因此HandlerThread不適合加入網(wǎng)絡(luò)操作揭朝。
問題思考
Android如何保證在請求繪制流程過程中,不會(huì)因?yàn)槠渌⑻幚硎录淖枞剑瑢?dǎo)致Vsync刷新信號的處理延時(shí)潭袱?
- 這其實(shí)就是Handler如何做到線程優(yōu)先級的問題。我們來跟進(jìn)一下繪制流程的處理锋恬。
- 頁面請求繪制流程刷新屯换,會(huì)調(diào)用View的 requestLayout / invalidate 方法。
- 最終會(huì)調(diào)入到ViewRootImpl的 scheduleTraversals 方法与学。
- 在 scheduleTraversals 方法中彤悔,首先在Handler消息隊(duì)列中插入同步屏障。
- 然后在 scheduleTraversals方法中向Choreographer(Vsync處理引擎)注冊Vsync刷新信號回調(diào)索守。
- 在Choreographer中晕窑,如果有跨線程通信,那么都會(huì)用異步消息卵佛,并且放在消息隊(duì)列頭部杨赤,消息不會(huì)堵塞敞斋,優(yōu)先處理。
- DisplayEventReceiver接收到native側(cè)的調(diào)用 dispatchVsync()疾牲,然后通過跨線程植捎,將onVsync()轉(zhuǎn)到doFrame()。
- doFrame()已經(jīng)在主線程中了阳柔,通過回調(diào) scheduleTraversals 在 Choreographer 注冊的回調(diào)Runnable焰枢。
- 返回ViewRootImpl中注冊了Vsync刷新信號回調(diào)的Runnable中,會(huì)走到 doTraversal 舌剂。
- 在doTraversal中医咨,移除同步屏障,然后執(zhí)行真正的繪制分發(fā)流程 performTraversals(繪制流程分發(fā)總方法)架诞。
- 等待繪制流程處理結(jié)束,才會(huì)進(jìn)行下一個(gè)消息的輪詢干茉,這樣就保證了繪制流程的高優(yōu)先級線程處理谴忧。
- 設(shè)置同步屏障是hide方法,只給系統(tǒng)來用角虫,所以設(shè)計(jì)初衷就是保證系統(tǒng)的某些操作要高優(yōu)先級沾谓。
- 線程的高優(yōu)先級需要兩個(gè)必不可少的條件 = 同步屏障 + 異步消息。
?