帶著這篇去通關(guān)所有Handler的提問(三)

帶著這篇去通關(guān)所有Handler的提問(三)

開心一刻

寫在前面:

大家久等了,前陣花了一周的時(shí)間去畢業(yè)旅行,所以更新就拖延了一陣,話不多說,我們來回顧一下本系列的前兩篇文章的思路知識(shí)點(diǎn)

Android消息機(jī)制字典型探究(一)

在第一篇文章中,我們總結(jié)了Android系統(tǒng)不允許在子線程更新UI的原因,本質(zhì)上是線程安全問題,從而引出了Handler。

Android消息機(jī)制字典型探究(二)

在第二篇文章中,我們又分析了三種在子線程更新UI的方法,分別是:View.post(param); Activity.runOnUIThread(param); Handler,當(dāng)我們對(duì)這三種方法的源碼進(jìn)一步分析發(fā)現(xiàn),其實(shí)都是對(duì)Handler做了一些封裝,所以本文我們就來正式全面探究有關(guān)Handler的知識(shí)點(diǎn)。

當(dāng)時(shí)我去面試的四家公司,都問到了Handler的相關(guān)知識(shí),有深有淺,所以重要程度不言而喻。面試官拿起你的簡歷,讓你談?wù)凥andler,你僅僅在表象上回答了Android線程通信的機(jī)理,然后面試官緊接著問了你如下的幾個(gè)問題:

  • 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ū)別。

Windows進(jìn)程消息模型
Android進(jìn)程消息模型

通過消息機(jī)制圖的對(duì)比,Windows消息處理模型中,存在一個(gè)系統(tǒng)的消息隊(duì)列,這個(gè)隊(duì)列是整個(gè)進(jìn)程的核心,幾乎所有的動(dòng)作都要轉(zhuǎn)換成消息,然后放到這個(gè)隊(duì)列中,由主線程統(tǒng)一處理。

而Android沒有全局的消息隊(duì)列,消息隊(duì)列是和某個(gè)線程相關(guān)聯(lián)在一起的。每個(gè)線程最多有一個(gè)消息隊(duì)列,消息的取出和處理,也在這個(gè)線程本身中完成。

也就是說,Android中,如果你想在當(dāng)前線程使用消息模型,則必須構(gòu)建一個(gè)消息隊(duì)列,而消息機(jī)制的相關(guān)主要類是:Looper、Handler、MessageQueue、Message。

我們并不著急去翻看這些類的源碼,理清楚底層實(shí)現(xiàn)的邏輯,而且先在宏觀表象上看看,Android消息機(jī)制是如何運(yùn)行的?

Android消息機(jī)制的宏觀原理

先來看一張Android消息處理類之間的關(guān)系圖

Android消息處理機(jī)制

我們從表象上解釋一下原理,Handler負(fù)責(zé)將Message發(fā)送至當(dāng)前線程的MessageQueue中,Looper時(shí)時(shí)刻刻監(jiān)視著MessageQueue,將符合時(shí)間要求的Message取出,再帶給發(fā)送消息的那個(gè)Handler通過HandleMessage處理。

對(duì)于消息機(jī)制的理解不能僅僅停留在這一步,下面我們從源碼的角度分析一下具體的邏輯細(xì)節(jié)。

Android消息機(jī)制相關(guān)類的源碼分析

其實(shí)寫這篇文章之前,我就一直在思考,站在什么角度展開這個(gè)機(jī)制的描述,更容易讓大家理解接受。思來想去,我覺得還是以一個(gè)Message游歷的形式去描寫,會(huì)顯著有趣和清晰一點(diǎn)。

Message:

人在邊境X(子線程)服役的士兵Message慵懶得躺在一個(gè)人數(shù)為50(池中最大數(shù)量)的軍營(Message池)中。不料這時(shí)突然接到了上司的obtain()命令(據(jù)說obtain命令更加節(jié)省軍費(fèi)),讓他去首都(主線程)告訴中央領(lǐng)導(dǎo)一些神秘代碼。小Message慌亂地整理了下衣角和帽子,帶上信封,準(zhǔn)備出發(fā)。

上司讓士兵Message收拾完畢之后等待一個(gè)神秘人的電話,并且囑咐他:到了首都之后,0是這次任務(wù)的暗號(hào)。

Message的創(chuàng)建和攜帶信息

Message是消息的載體,Message設(shè)計(jì)成為Parcelable類的派生類,這表明Message可以通過binder來跨進(jìn)程發(fā)送。
通常我們都會(huì)用obtain()方法去創(chuàng)建Message,如果消息池中有Message有,則取出,沒有,再重新創(chuàng)建。這樣可以防止對(duì)象的重復(fù)創(chuàng)建,節(jié)省資源。

obtain方法源碼

"鈴鈴鈴..."小Message接到了一個(gè)陌生男子的電話。
“我叫handler,來自activity大本營,是你這次任務(wù)的接受者,一會(huì)我?guī)闳ナ锥嫉南⒅行娜?bào)道?!?/p>

Handler

來自Activity大本營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。

Activity中定義了Handler

在Handler的構(gòu)造中,默認(rèn)完成了對(duì)當(dāng)前線程Looper的綁定,至于Looper是誰,一會(huì)再談。

Handler的構(gòu)造方法

通過Looper.myLooper()獲取了當(dāng)前線程保存的Looper實(shí)例,又通過mLooper.mQueue獲取了Looper中的MessageQueue實(shí)例。在此時(shí),mhandler實(shí)例與looper和messageQueue實(shí)例,關(guān)聯(lián)上了。

mHandler神情驕傲得對(duì)小Message說:我已經(jīng)跟首都的消息中心打好了招呼,準(zhǔn)備接收你了,現(xiàn)在有兩種車,一種車名叫“send”,一種叫“post”,你想坐哪輛去首都都可以,不過要根據(jù)你上司的命令,選擇車種類下對(duì)應(yīng)的型號(hào)哦~

  • send

    這里寫圖片描述

  • post

    這里寫圖片描述

從代碼的實(shí)現(xiàn)上來看,post方法也是在使用send類的方法在發(fā)送消息,只是他們的參數(shù)要求是Runnable對(duì)象。

通過對(duì)Handler源碼的分析,發(fā)現(xiàn)除了sendMessageAtFrontOfQueue方法之外,其余任何send的相關(guān)方法,都經(jīng)過層層包裝走到了sendMessageAtTime方法中,我們來看看源碼:

sendMessageAtTime源碼

這時(shí)小Message和mHandler一同上了車牌號(hào)為“sendMessage”的車,行駛在一條叫“enqueueMessage”的高速公路上,mHandler向一無所知的小Message介紹說,每個(gè)像他一樣的Message都是通過enqueueMessage路進(jìn)入MessageQueue的。我們是要去首都的MessageQueue中心,其實(shí)你的消息到時(shí)候也是我處理的,不過現(xiàn)在還不是時(shí)候哦,因?yàn)槲液苊Α?/p>

enqueueMessage源碼

enqueueMessage是MessageQueue的方法,用來將Message根據(jù)時(shí)間排序,放入到MessageQueue中。其中msg.target = this,是保證每個(gè)發(fā)送Message的Handler也能處理這個(gè)Message。

Looper

路上的時(shí)間不短不長,mHandler依然為小Message熱心介紹著MessageQueue和Looper
“在每個(gè)駐扎地(線程)中,只有一個(gè)MessageQueue和一個(gè)Looper,他們兩個(gè)是相殺相愛,同生共死的好基友,Looper是個(gè)跑不死的郵差,一直負(fù)責(zé)取出MessageQueue中的Message”
"不過通常只有首都(主線程)的Looper和MessageQueue是創(chuàng)建好的,其他地方需要我們?nèi)藶榈貏?chuàng)建哦~"

prepare方法

Looper類提供了prepare方法來創(chuàng)建Looper。可以看到,當(dāng)重復(fù)創(chuàng)建Looper時(shí),會(huì)拋出異常,也就是說,每個(gè)線程只有一個(gè)Looper。

Looper構(gòu)造

緊接著在Looper的構(gòu)造方法中,又創(chuàng)建了與它一一對(duì)應(yīng)的MessageQueue,既然Looper在一個(gè)線程中是唯一的,所以MessageQueue也是唯一的。

在Android中,ActivityThread的main方法是程序的入口,主線程的Looper和MessageQueue就是在此時(shí)創(chuàng)建的。

ActivityThread的main方法

可以看到,在main方法中,既創(chuàng)建了Looper,也調(diào)用了Looper.loop()方法。

mHandler和小Message通過enqueueMessage路來到了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方法

分析一下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ì)他說:“喔,又是mHandler帶來的人啊,那把你交給他處理了”

忐忑不安的小Message看到了一個(gè)熟悉的身影,mHandler就在面前,顯然mHandler有些健忘,可能是接觸了太多跟小Message一樣的人,為了讓mHandler想起自己,小Message說出了上司交給他的暗號(hào)0.

dispatchMessage方法

可以看見dispatchMessage方法中的邏輯比較簡單,具體就是如果mCallback不為空,則調(diào)用mCallback的handleMessage()方法,否則直接調(diào)用Handler的handleMessage()方法,并將消息對(duì)象作為參數(shù)傳遞過去。

在handlerMessage()方法中,小Message出色的完成了自己的任務(wù)。

寫在后面:

下一篇中,我們會(huì)探討一下為什么loop方法中for死循環(huán)不會(huì)造成ANR,有一些有關(guān)Handler的使用技巧,以及可能造成的內(nèi)存泄漏,敬請(qǐng)期待。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,716評(píng)論 25 709
  • 1. ANR異常 Application No Response:應(yīng)用程序無響應(yīng)。在主線程中,是不允許執(zhí)行耗時(shí)的操...
    JackChen1024閱讀 1,586評(píng)論 0 3
  • 異步消息處理線程啟動(dòng)后會(huì)進(jìn)入一個(gè)無限的循環(huán)體之中,每循環(huán)一次,從其內(nèi)部的消息隊(duì)列中取出一個(gè)消息,然后回調(diào)相應(yīng)的消息...
    cxm11閱讀 6,522評(píng)論 2 39
  • Android消息處理機(jī)制估計(jì)都被寫爛了,但是依然還是要寫一下,因?yàn)锳ndroid應(yīng)用程序是通過消息來驅(qū)動(dòng)的,An...
    一碼立程閱讀 4,591評(píng)論 4 36
  • 昨天去清真寺下面買肉。從外面剛走肉鋪里,真有些受不了濃郁的膻腥味,但不過片刻也就習(xí)慣。鋪?zhàn)觾蛇叺陌缸由戏峙胖蠖训?..
    鉛筆芒種閱讀 680評(píng)論 0 1

友情鏈接更多精彩內(nèi)容