帶著這篇去通關(guān)所有Handler的提問(wèn)(三)
寫在前面:
大家久等了尔当,前陣花了一周的時(shí)間去畢業(yè)旅行屋休,所以更新就拖延了一陣霞怀,話不多說(shuō)欢嘿,我們來(lái)回顧一下本系列的前兩篇文章的思路和知識(shí)點(diǎn):
在第一篇文章中晃跺,我們總結(jié)了Android系統(tǒng)不允許在子線程更新UI的原因揩局,本質(zhì)上是線程安全問(wèn)題,從而引出了Handler掀虎。
在第二篇文章中凌盯,我們又分析了三種在子線程更新UI的方法,分別是:View.post(param); Activity.runOnUIThread(param); Handler烹玉,當(dāng)我們對(duì)這三種方法的源碼進(jìn)一步分析發(fā)現(xiàn)驰怎,其實(shí)都是對(duì)Handler做了一些封裝,所以本文我們就來(lái)正式全面探究有關(guān)Handler的知識(shí)點(diǎn)二打。
當(dāng)時(shí)我去面試的四家公司县忌,都問(wèn)到了Handler的相關(guān)知識(shí),有深有淺,所以重要程度不言而喻症杏。面試官拿起你的簡(jiǎn)歷装获,讓你談?wù)凥andler,你僅僅在表象上回答了Android線程通信的機(jī)理厉颤,然后面試官緊接著問(wèn)了你如下的幾個(gè)問(wèn)題:
Handler是屬于哪個(gè)類的穴豫?
Handler、Looper逼友、MessageQueue何時(shí)建立的相互關(guān)系绩郎?
主線程的Looper和MessageQueue是何時(shí)創(chuàng)建的?
在同一線程中翁逞,Looper和MessageQueue是怎樣的數(shù)量對(duì)應(yīng)關(guān)系,與Handler又是怎樣的數(shù)量對(duì)應(yīng)關(guān)系溉仑?
MessageQueue中消息為空挖函,線程阻塞掛起等待,為什么不會(huì)造成ANR浊竟?
有關(guān)Handler的內(nèi)存泄漏是怎么一回事怨喘?
so...光知道表象很可能是不夠的,而且還給自己挖了一個(gè)坑振定,所以我們對(duì)于一個(gè)知識(shí)點(diǎn)的探尋要全面充分一點(diǎn)必怜。下面正式開始本文。
Windows和Android消息機(jī)制的區(qū)別
現(xiàn)在的操作系統(tǒng)普遍采用消息驅(qū)動(dòng)模式后频。Windows操作系統(tǒng)就是典型的消息驅(qū)動(dòng)模型梳庆。但是,Android的消息處理機(jī)制和Windows的消息處理機(jī)制又不太相同卑惜。我給大家畫了圖膏执,看看二者的區(qū)別。
通過(guò)消息機(jī)制圖的對(duì)比露久,Windows消息處理模型中更米,存在一個(gè)系統(tǒng)的消息隊(duì)列,這個(gè)隊(duì)列是整個(gè)進(jìn)程的核心毫痕,幾乎所有的動(dòng)作都要轉(zhuǎn)換成消息征峦,然后放到這個(gè)隊(duì)列中,由主線程統(tǒng)一處理消请。
而Android沒(méi)有全局的消息隊(duì)列栏笆,消息隊(duì)列是和某個(gè)線程相關(guān)聯(lián)在一起的。每個(gè)線程最多有一個(gè)消息隊(duì)列臊泰,消息的取出和處理竖伯,也在這個(gè)線程本身中完成。
也就是說(shuō),Android中七婴,如果你想在當(dāng)前線程使用消息模型祟偷,則必須構(gòu)建一個(gè)消息隊(duì)列,而消息機(jī)制的相關(guān)主要類是:Looper打厘、Handler修肠、MessageQueue、Message户盯。
我們并不著急去翻看這些類的源碼嵌施,理清楚底層實(shí)現(xiàn)的邏輯,而且先在宏觀表象上看看莽鸭,Android消息機(jī)制是如何運(yùn)行的吗伤?
Android消息機(jī)制的宏觀原理
先來(lái)看一張Android消息處理類之間的關(guān)系圖
我們從表象上解釋一下原理,Handler負(fù)責(zé)將Message發(fā)送至當(dāng)前線程的MessageQueue中硫眨,Looper時(shí)時(shí)刻刻監(jiān)視著MessageQueue足淆,將符合時(shí)間要求的Message取出,再帶給發(fā)送消息的那個(gè)Handler通過(guò)HandleMessage處理礁阁。
對(duì)于消息機(jī)制的理解不能僅僅停留在這一步巧号,下面我們從源碼的角度分析一下具體的邏輯細(xì)節(jié)。
Android消息機(jī)制相關(guān)類的源碼分析
其實(shí)寫這篇文章之前姥闭,我就一直在思考丹鸿,站在什么角度展開這個(gè)機(jī)制的描述,更容易讓大家理解接受棚品。思來(lái)想去靠欢,我覺得還是以一個(gè)Message游歷的形式去描寫,會(huì)顯著有趣和清晰一點(diǎn)铜跑。
Message:
人在邊境X(子線程)服役的士兵Message慵懶得躺在一個(gè)人數(shù)為50(池中最大數(shù)量)的軍營(yíng)(Message池)中掺涛。不料這時(shí)突然接到了上司的obtain()命令(據(jù)說(shuō)obtain命令更加節(jié)省軍費(fèi)),讓他去首都(主線程)告訴中央領(lǐng)導(dǎo)一些神秘代碼疼进。小Message慌亂地整理了下衣角和帽子薪缆,帶上信封,準(zhǔn)備出發(fā)伞广。
上司讓士兵Message收拾完畢之后等待一個(gè)神秘人的電話拣帽,并且囑咐他:到了首都之后,0是這次任務(wù)的暗號(hào)嚼锄。
Message是消息的載體减拭,Message設(shè)計(jì)成為Parcelable類的派生類,這表明Message可以通過(guò)binder來(lái)跨進(jìn)程發(fā)送区丑。
通常我們都會(huì)用obtain()方法去創(chuàng)建Message拧粪,如果消息池中有Message有修陡,則取出,沒(méi)有可霎,再重新創(chuàng)建魄鸦。這樣可以防止對(duì)象的重復(fù)創(chuàng)建,節(jié)省資源癣朗。
"鈴鈴鈴..."小Message接到了一個(gè)陌生男子的電話拾因。
“我叫handler,來(lái)自activity大本營(yíng)旷余,是你這次任務(wù)的接受者绢记,一會(huì)我?guī)闳ナ锥嫉南⒅行娜?bào)道≌裕”
Handler
來(lái)自Activity大本營(yíng)Handler部門是整個(gè)消息機(jī)制系統(tǒng)的核心部門蠢熄,當(dāng)然部門下有很多個(gè) Handler,這次協(xié)助小Message任務(wù)的叫mHandler炉旷。Handler部門下的員工都有一個(gè)特點(diǎn)签孔,就是只關(guān)心自己的message。
Handler屬于Activity砾跃,創(chuàng)建任何一個(gè)Handler都屬于重寫了Activity中的Handler。
在Handler的構(gòu)造中节吮,默認(rèn)完成了對(duì)當(dāng)前線程Looper的綁定抽高,至于Looper是誰(shuí),一會(huì)再談透绩。
通過(guò)Looper.myLooper()獲取了當(dāng)前線程保存的Looper實(shí)例翘骂,又通過(guò)mLooper.mQueue獲取了Looper中的MessageQueue實(shí)例。在此時(shí)帚豪,mhandler實(shí)例與looper和messageQueue實(shí)例碳竟,關(guān)聯(lián)上了。
mHandler神情驕傲得對(duì)小Message說(shuō):我已經(jīng)跟首都的消息中心打好了招呼狸臣,準(zhǔn)備接收你了莹桅,現(xiàn)在有兩種車,一種車名叫“send”烛亦,一種叫“post”诈泼,你想坐哪輛去首都都可以,不過(guò)要根據(jù)你上司的命令煤禽,選擇車種類下對(duì)應(yīng)的型號(hào)哦~
-
send
-
post
從代碼的實(shí)現(xiàn)上來(lái)看铐达,post方法也是在使用send類的方法在發(fā)送消息,只是他們的參數(shù)要求是Runnable對(duì)象檬果。
通過(guò)對(duì)Handler源碼的分析瓮孙,發(fā)現(xiàn)除了sendMessageAtFrontOfQueue方法之外唐断,其余任何send的相關(guān)方法,都經(jīng)過(guò)層層包裝走到了sendMessageAtTime方法中杭抠,我們來(lái)看看源碼:
這時(shí)小Message和mHandler一同上了車牌號(hào)為“sendMessage”的車脸甘,行駛在一條叫“enqueueMessage”的高速公路上,mHandler向一無(wú)所知的小Message介紹說(shuō)祈争,每個(gè)像他一樣的Message都是通過(guò)enqueueMessage路進(jìn)入MessageQueue的斤程。我們是要去首都的MessageQueue中心,其實(shí)你的消息到時(shí)候也是我處理的菩混,不過(guò)現(xiàn)在還不是時(shí)候哦忿墅,因?yàn)槲液苊Α?/p>
enqueueMessage是MessageQueue的方法,用來(lái)將Message根據(jù)時(shí)間排序沮峡,放入到MessageQueue中疚脐。其中msg.target = this,是保證每個(gè)發(fā)送Message的Handler也能處理這個(gè)Message邢疙。
Looper
路上的時(shí)間不短不長(zhǎng)棍弄,mHandler依然為小Message熱心介紹著MessageQueue和Looper
“在每個(gè)駐扎地(線程)中,只有一個(gè)MessageQueue和一個(gè)Looper疟游,他們兩個(gè)是相殺相愛呼畸,同生共死的好基友,Looper是個(gè)跑不死的郵差颁虐,一直負(fù)責(zé)取出MessageQueue中的Message”
"不過(guò)通常只有首都(主線程)的Looper和MessageQueue是創(chuàng)建好的蛮原,其他地方需要我們?nèi)藶榈貏?chuàng)建哦~"
Looper類提供了prepare方法來(lái)創(chuàng)建Looper×砑ǎ可以看到儒陨,當(dāng)重復(fù)創(chuàng)建Looper時(shí),會(huì)拋出異常笋籽,也就是說(shuō)蹦漠,每個(gè)線程只有一個(gè)Looper。
緊接著在Looper的構(gòu)造方法中车海,又創(chuàng)建了與它一一對(duì)應(yīng)的MessageQueue笛园,既然Looper在一個(gè)線程中是唯一的,所以MessageQueue也是唯一的侍芝。
在Android中喘沿,ActivityThread的main方法是程序的入口,主線程的Looper和MessageQueue就是在此時(shí)創(chuàng)建的竭贩。
可以看到蚜印,在main方法中,既創(chuàng)建了Looper留量,也調(diào)用了Looper.loop()方法窄赋。
mHandler和小Message通過(guò)enqueueMessage路來(lái)到了MessageQueue中哟冬,進(jìn)入之前,門衛(wèi)仔仔細(xì)細(xì)地給小Message貼上了以下標(biāo)簽:
“mHandler負(fù)責(zé)帶入”
“處理時(shí)間為0ms”
并且告訴小Message忆绰,一定要按照時(shí)間順序排隊(duì)浩峡。
進(jìn)入隊(duì)伍中,Looper大哥正在不辭辛勞的將一個(gè)又一個(gè)跟小Message一樣的士兵帶走错敢。
分析一下loop方法翰灾,有一個(gè)for的死循環(huán),不斷地調(diào)用queue.next方法稚茅,在消息隊(duì)列中取Message纸淮。并且在Message中取出target,這個(gè)target其實(shí)就是發(fā)送消息的handler亚享,調(diào)用它的dispatchMessage方法咽块。
首都的MessageQueue中心雖然人很多,但是大家都井井有條的排著隊(duì)伍欺税,Looper老哥看了一眼手里的名單侈沪,叫到了小Message的名字,看了一眼小Message身上的標(biāo)簽晚凿,對(duì)他說(shuō):“喔亭罪,又是mHandler帶來(lái)的人啊,那把你交給他處理了”
忐忑不安的小Message看到了一個(gè)熟悉的身影歼秽,mHandler就在面前应役,顯然mHandler有些健忘,可能是接觸了太多跟小Message一樣的人哲银,為了讓mHandler想起自己扛吞,小Message說(shuō)出了上司交給他的暗號(hào)0.
可以看見dispatchMessage方法中的邏輯比較簡(jiǎn)單呻惕,具體就是如果mCallback不為空荆责,則調(diào)用mCallback的handleMessage()方法,否則直接調(diào)用Handler的handleMessage()方法亚脆,并將消息對(duì)象作為參數(shù)傳遞過(guò)去做院。
在handlerMessage()方法中,小Message出色的完成了自己的任務(wù)濒持。
寫在后面:
下一篇中键耕,我們會(huì)探討一下為什么loop方法中for死循環(huán)不會(huì)造成ANR,有一些有關(guān)Handler的使用技巧柑营,以及可能造成的內(nèi)存泄漏屈雄,敬請(qǐng)期待。