Android 消息機(jī)制 Handler

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)一下繪制流程的處理锋恬。
    1. 頁面請求繪制流程刷新屯换,會(huì)調(diào)用View的 requestLayout / invalidate 方法。
    1. 最終會(huì)調(diào)入到ViewRootImpl的 scheduleTraversals 方法与学。
    1. 在 scheduleTraversals 方法中彤悔,首先在Handler消息隊(duì)列中插入同步屏障。
    1. 然后在 scheduleTraversals方法中向Choreographer(Vsync處理引擎)注冊Vsync刷新信號回調(diào)索守。
    1. 在Choreographer中晕窑,如果有跨線程通信,那么都會(huì)用異步消息卵佛,并且放在消息隊(duì)列頭部杨赤,消息不會(huì)堵塞敞斋,優(yōu)先處理。
    1. DisplayEventReceiver接收到native側(cè)的調(diào)用 dispatchVsync()疾牲,然后通過跨線程植捎,將onVsync()轉(zhuǎn)到doFrame()。
    1. doFrame()已經(jīng)在主線程中了阳柔,通過回調(diào) scheduleTraversals 在 Choreographer 注冊的回調(diào)Runnable焰枢。
    1. 返回ViewRootImpl中注冊了Vsync刷新信號回調(diào)的Runnable中,會(huì)走到 doTraversal 舌剂。
    1. 在doTraversal中医咨,移除同步屏障,然后執(zhí)行真正的繪制分發(fā)流程 performTraversals(繪制流程分發(fā)總方法)架诞。
    1. 等待繪制流程處理結(jié)束,才會(huì)進(jìn)行下一個(gè)消息的輪詢干茉,這樣就保證了繪制流程的高優(yōu)先級線程處理谴忧。
    1. 設(shè)置同步屏障是hide方法,只給系統(tǒng)來用角虫,所以設(shè)計(jì)初衷就是保證系統(tǒng)的某些操作要高優(yōu)先級沾谓。
    1. 線程的高優(yōu)先級需要兩個(gè)必不可少的條件 = 同步屏障 + 異步消息。

?

小編的擴(kuò)展鏈接

《Android 基礎(chǔ)組件 全家桶》
?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末戳鹅,一起剝皮案震驚了整個(gè)濱河市均驶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌枫虏,老刑警劉巖妇穴,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異隶债,居然都是意外死亡腾它,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門死讹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瞒滴,“玉大人,你說我怎么就攤上這事赞警〖巳蹋” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵愧旦,是天一觀的道長世剖。 經(jīng)常有香客問我,道長笤虫,這世上最難降的妖魔是什么搁廓? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任引颈,我火速辦了婚禮,結(jié)果婚禮上境蜕,老公的妹妹穿的比我還像新娘蝙场。我一直安慰自己,他們只是感情好粱年,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布售滤。 她就那樣靜靜地躺著,像睡著了一般台诗。 火紅的嫁衣襯著肌膚如雪完箩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天拉队,我揣著相機(jī)與錄音弊知,去河邊找鬼。 笑死粱快,一個(gè)胖子當(dāng)著我的面吹牛秩彤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播事哭,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼漫雷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鳍咱?” 一聲冷哼從身側(cè)響起降盹,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谤辜,沒想到半個(gè)月后蓄坏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡丑念,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年剑辫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渠欺。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡妹蔽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挠将,到底是詐尸還是另有隱情胳岂,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布舔稀,位于F島的核電站乳丰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏内贮。R本人自食惡果不足惜产园,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一汞斧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧什燕,春花似錦粘勒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至技俐,卻和暖如春乘陪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背雕擂。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工啡邑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人井赌。 一個(gè)月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓谤逼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親族展。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

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