消息處理機制

本文主要包含:
1轻掩、推薦主線程更新UI的原因。(Android單線程模型)
2懦底、消息處理機制的原理唇牧。
3、非UI線程真的不能更新UI嗎?

在子線程中更新UI會報錯:Only the original thread that created a view hierarchy can touch its views.丐重,原因是ViewRootImpl的checkThread()方法做了檢查腔召,只有主線程才能更新UI。
子線程不能更新UI的原因:UI線程不是線程安全的扮惦,多線程并發(fā)操作UI會造成UI混亂臀蛛;加鎖會造成效率低下⊙旅郏基于這兩點采用單線程處理UI操作浊仆,通過Handler切換進程。

消息分發(fā)機制就是消息的分發(fā)和處理過程豫领;簡單的說就是:handler作為消息的發(fā)送者和處理者抡柿,MessageQueen是消息的載體,Looper不斷從MessageQueen中取出消息交由Handler來處理氏堤。

我們平常使用消息處理機制來更新UI一般都是在主線程中new一個Handler重寫handleMessage方法沙绝,在該方法中做更新UI的操作。在使用Handler發(fā)送消息之前必須要looper.prepare(),否則會拋出異常鼠锈。下面分析一下消息的分發(fā)和處理:
new一個Handler分為在主線程和子線程創(chuàng)建Handler實例,如果在子線程中創(chuàng)建Handler實例星著,必須在之前調(diào)用Looper的prepare方法創(chuàng)建一個Looper购笆,否則會報錯。原因是:

B{A3T6MY96{(Z_P95(JGW@T.png

在主線程不需要調(diào)用Looper的prepare的原因是:在程序啟動的時候虚循,ActivityThread為我們創(chuàng)建了Looper同欠,并調(diào)用了loop()方法。ActivityThread的main()方法中調(diào)用了Looper.prepareMainLooper()和Looper.loop();因此不需要我們自己手動調(diào)用横缔。

Looper:
prepare():先從ThreadLocal中取looper铺遂,如果存在looper就會拋出異常,保證一個線程只有一個looper實例(prepare方法只允許執(zhí)行一次)茎刚;創(chuàng)建一個looper實例放入ThreadLocal中襟锐;在構造Looper實例的時候,會創(chuàng)建一個MessageQueen和獲得當前線程膛锭。

Looper的prepare方法:
![LZU5GO3X$YIJX2G~0E0]JLH.png](http://upload-images.jianshu.io/upload_images/2578759-f1f9e026c98dd136.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
Looper的構造方法:

C($5R22Y%(KUER{}$L)DE49.png

接下來是Handler的sendMessage()和post()方法:
sendMessage()方法最終會調(diào)用enqueueMessage(),將發(fā)送的消息插入到MessageQueue中:


KUWMN)3(BRU)S5JZ3@D)48U.png

Handler的post(new Runnable()):實際上和sendMessage方法是相同的粮坞,只是將runnable賦值給了message的callBack對象。

![X6M]D%ZJHBVSSJ5GKM496FG.png](http://upload-images.jianshu.io/upload_images/2578759-8c350485fd8e0702.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

J_{{M13QYY2B}4{BC}BDXZR.png

接下來是最重要的方法loop():首先拿到當前線程的Looper實例和該Looper對應的MessageQueen初狰;然后進入無限循環(huán)莫杈,不斷的從MessageQueen中取出消息,如果消息為空就阻塞等待奢入,如果消息不為空就通過meg.target.dispatchMessage(msg)將該消息交給handler處理(這時才是真正的消息處理階段)筝闹。
接下來看一下handler的dispatchMessage方法:最終消息交由runnable的run方法直接執(zhí)行或者handleMessage()方法進行處理。

![IDH}8]O2WKHY}G{FMQ_@_J2.png](http://upload-images.jianshu.io/upload_images/2578759-9299c4785dab7f02.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

最后再來看一下Activity中的runOnUiThread()方法:如果當前的線程不等于UI線程(主線程),就去調(diào)用Handler的post()方法关顷,否則就直接調(diào)用Runnable對象的run()方法糊秆。
![4%NAKOD2EG3`0~$]DYMQJ@K.png](http://upload-images.jianshu.io/upload_images/2578759-9b7cb1d852ff18ec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

非UI線程真的不能更新UI嗎?其實在特殊情況下是可以更新UI的解寝,比如在onCreate方法中開啟子線程更新UI就不會報錯扩然,原因是checkThread方法是在onResume之后執(zhí)行的,在onResume之前ViewRootImpl還沒有創(chuàng)建完成聋伦。但是我們通常不在onCreate中開啟子線程做更新UI的操作夫偶。
更新UI的操作都是調(diào)用View的invalidate。在View的invalidate里面有這樣一段代碼:

![8Q0GR~D]}AKSZ_2@O0GQ%KV.png](http://upload-images.jianshu.io/upload_images/2578759-36b26cbe8963313c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
ViewParent是一個接口觉增,ViewRootImpl是ViewParent的具體實現(xiàn)兵拢,p.invalidateChild(this, damage)就是調(diào)用的ViewRootImpl的invalidateChild方法,該方法會調(diào)用checkThread方法逾礁,就是用于檢查更新UI的操作是否是在主線程说铃,如果不是在主線程就會拋出那個常見的異常。

QH6V@$AOYJ)NR9(G8AXMN%M.png

在onCreate方法中開啟子線程更新UI不報異常的原因就是在onCreate時ViewRootImpl還沒有創(chuàng)建完成嘹履,不會調(diào)用checkThread方法腻扇。
看一下ActivityThread的handleResumeActivity方法,該方法里面有一個performResumeActivity方法砾嫉,這個方法最終會回調(diào)Activity的onResume方法幼苛。而ViewRootImpl又是在什么時候創(chuàng)建的呢?handleResumeActivity中會調(diào)用windManager的addView方法焕刮,WindowManagerImpl是WindowManager的具體實現(xiàn)類舶沿,其中的addView方法會調(diào)用WindowManagerGlobal的addView方法,而ViewRoopImpl就是在此時創(chuàng)建的配并±ǖ矗總是ViewRootImpl的創(chuàng)建是在onResume方法之后,這就解釋了在onCreate開啟子線程更新UI不會出錯的原因溉旋。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末畸冲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子低滩,更是在濱河造成了極大的恐慌召夹,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恕沫,死亡現(xiàn)場離奇詭異监憎,居然都是意外死亡,警方通過查閱死者的電腦和手機婶溯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門鲸阔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來偷霉,“玉大人,你說我怎么就攤上這事褐筛±嗌伲” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵渔扎,是天一觀的道長硫狞。 經(jīng)常有香客問我,道長晃痴,這世上最難降的妖魔是什么残吩? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮倘核,結果婚禮上泣侮,老公的妹妹穿的比我還像新娘。我一直安慰自己紧唱,他們只是感情好活尊,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著漏益,像睡著了一般蛹锰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绰疤,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天宁仔,我揣著相機與錄音,去河邊找鬼峦睡。 笑死,一個胖子當著我的面吹牛权埠,可吹牛的內(nèi)容都是我干的榨了。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼攘蔽,長吁一口氣:“原來是場噩夢啊……” “哼龙屉!你這毒婦竟也來了?” 一聲冷哼從身側響起满俗,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤转捕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后唆垃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體五芝,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡芦瘾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年淘衙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娱挨。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖醉途,靈堂內(nèi)的尸體忽然破棺而出矾瑰,到底是詐尸還是另有隱情,我是刑警寧澤隘擎,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布殴穴,位于F島的核電站,受9級特大地震影響货葬,放射性物質發(fā)生泄漏采幌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一宝惰、第九天 我趴在偏房一處隱蔽的房頂上張望植榕。 院中可真熱鬧,春花似錦尼夺、人聲如沸尊残。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寝衫。三九已至,卻和暖如春拐邪,著一層夾襖步出監(jiān)牢的瞬間慰毅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工扎阶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留汹胃,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓东臀,卻偏偏與公主長得像着饥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子惰赋,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

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