異步消息處理機(jī)制,Android開發(fā)中必須要掌握的內(nèi)容扇商。作為面試中經(jīng)常會(huì)提問的知識(shí)點(diǎn),同時(shí)也是開發(fā)中經(jīng)常要打交道的部分宿礁,我們必須要對(duì)它透徹地研究案铺,明白實(shí)現(xiàn)原理,熟悉整個(gè)流程窘拯,以及Handler红且、Message、Looper等不同角色之間的關(guān)系涤姊。下面是自己對(duì)于這部分的學(xué)習(xí)總結(jié)暇番。
一、背景介紹
二思喊、異步消息處理機(jī)制概述
三壁酬、示例與解析
四、Handler詳解
五、Message與MessageQueue詳解
六舆乔、Looper詳解
七岳服、異步消息處理機(jī)制總結(jié)
一、背景介紹
這部分是背景介紹希俩,我們用過Handler吊宋,用過Message,可能了解過Looper等等颜武,但是Android為什么要有這一套異步消息處理機(jī)制璃搜?
當(dāng)一個(gè)應(yīng)用啟動(dòng)時(shí),如果它沒有其它組件在運(yùn)行的話鳞上,Android系統(tǒng)會(huì)為這個(gè)應(yīng)用創(chuàng)建一個(gè)Linux進(jìn)程这吻,進(jìn)程里有一個(gè)執(zhí)行線程(這個(gè)執(zhí)行線程叫主線程,也稱為UI線程)篙议。默認(rèn)情況下唾糯,同一個(gè)應(yīng)用的所有組件都運(yùn)行在相同的進(jìn)程和線程里。
系統(tǒng)絕對(duì)不會(huì)為一個(gè)組件的實(shí)例創(chuàng)建單獨(dú)的線程鬼贱。運(yùn)行于同一進(jìn)程的所有組件均在 UI 線程中實(shí)例化移怯,并且對(duì)每個(gè)組件的系統(tǒng)調(diào)用均由該線程進(jìn)行分發(fā)。因此吩愧,響應(yīng)系統(tǒng)回調(diào)的方法(例如芋酌,報(bào)告用戶操作的 onKeyDown()方法或生命周期回調(diào)方法)始終在進(jìn)程的 UI 線程中運(yùn)行。
Android 的單線程模式有兩個(gè)務(wù)必要遵守的原則雁佳,一是不要阻塞 UI 線程,二是不要在 UI 線程之外訪問Android這些界面UI元素同云。
到這里糖权,我們應(yīng)該初步了解了什么是UI線程,Android的單線程模式炸站。但是星澳,在開發(fā)過程中我們可能會(huì)有這樣的需要,想要聯(lián)網(wǎng)獲取數(shù)據(jù)旱易,然后將數(shù)據(jù)顯示到界面上禁偎,因?yàn)槁?lián)網(wǎng)獲取數(shù)據(jù)是個(gè)比較耗時(shí)的任務(wù),為了不阻塞UI線程阀坏,我們需要?jiǎng)?chuàng)建一個(gè)新的線程來進(jìn)行數(shù)據(jù)獲取如暖,但是這樣就會(huì)造成數(shù)據(jù)獲取到的時(shí)候不是在UI線程里的,我們不能直接在非UI線程里更新界面忌堂,所以要把數(shù)據(jù)發(fā)送到UI線程里盒至。怎么發(fā)送呢?這時(shí),異步消息處理機(jī)制就出現(xiàn)了枷遂。
二樱衷、異步消息處理機(jī)制概述
想要搞清楚異步消息處理機(jī)制,我們必須要搞清楚它的幾個(gè)關(guān)鍵知識(shí)點(diǎn)之間的關(guān)系酒唉,包括Handler矩桂、Message、MessageQueue痪伦、Looper耍鬓。
下面先對(duì)整個(gè)流程總體上有個(gè)把握。我們以上面提到的聯(lián)網(wǎng)獲取數(shù)據(jù)的需求為例流妻。
首先牲蜀,我們想要聯(lián)網(wǎng)獲取數(shù)據(jù),考慮到聯(lián)網(wǎng)獲取數(shù)據(jù)可能是個(gè)耗時(shí)的任務(wù)绅这,我們需要開啟一個(gè)新的線程涣达,在其中完成操作。開啟線程的方法有好幾種证薇,但是經(jīng)常使用的是先定義一個(gè)實(shí)現(xiàn)接口Runable的類度苔,然后在方法run()里訪問網(wǎng)絡(luò)數(shù)據(jù),最后在需要的地方實(shí)例化Thread對(duì)象浑度,傳入之前定義的類的實(shí)例寇窑,調(diào)用方法start()來開啟線程。
上一步我們?cè)趓un方法里訪問網(wǎng)絡(luò)箩张,獲取到數(shù)據(jù)后需要把它發(fā)送到UI線程里甩骏。怎么發(fā)送,在創(chuàng)建線程之前先慷,我們先實(shí)例化一個(gè)Handler饮笛。這樣在run方法里我們可以通過實(shí)例化的Handler把數(shù)據(jù)發(fā)送到UI線程里,數(shù)據(jù)在發(fā)送過程中要用Message作為載體论熙。
Handler福青,直接翻譯就是處理器,它可以發(fā)送Message脓诡,也可以處理Message无午。在UI線程里實(shí)例化Handler,然后可以利用它把非UI線程里的Message發(fā)送到Message Queue里祝谚,之后它可以對(duì)從Message Queue里出來的消息進(jìn)行處理宪迟。
Looper,在平時(shí)的使用中我們一般會(huì)與Handler踊跟、Message打交道比較多踩验,但是Looper是背后運(yùn)行不可或缺的一個(gè)部分鸥诽,它的作用主要是不斷從MessageQueue中取出Message,分發(fā)給Handler箕憾,讓Handler處理牡借。
三、示例與解析
下面看一個(gè)例子袭异,可以很直觀地了解Handler和Message的用法钠龙。
-
實(shí)例化Handler。重寫handleMessage()方法完成對(duì)消息的處理御铃。
-
創(chuàng)建線程碴里。這里實(shí)現(xiàn)run方法,網(wǎng)絡(luò)請(qǐng)求如果不熟悉可以暫時(shí)忽略上真,重點(diǎn)看畫出來的部分咬腋,獲取到數(shù)據(jù)后先實(shí)例化好Message 最后通過Handler的sendMessage方法發(fā)送。
-
開啟線程
四睡互、Handler詳解
1根竿、概述
前面已經(jīng)提到,Handler可以譯為處理器就珠,它的主要作用有兩個(gè)寇壳,一是定時(shí)執(zhí)行Message和Runnable,二是讓一個(gè)執(zhí)行動(dòng)作進(jìn)入隊(duì)列妻怎,在另外的線程里執(zhí)行壳炎。
Handler允許我們發(fā)送和處理與線程的相關(guān)的Message以及Runnable對(duì)象。每個(gè)Handler實(shí)例都關(guān)聯(lián)著一個(gè)單線程逼侦,同時(shí)也關(guān)聯(lián)著那個(gè)線程的消息隊(duì)列匿辩。當(dāng)我們創(chuàng)建一個(gè)新的Handler時(shí),它就與創(chuàng)建它的線程以及線程的消息隊(duì)列相關(guān)聯(lián)偿洁,也就是說撒汉,它將會(huì)傳遞Message和Runnable給MessageQueue,并在它們從MessageQueue出來時(shí)執(zhí)行它們涕滋。
2、用法
Handler的第一個(gè)作用是定時(shí)執(zhí)行Message和Runnable挠阁,常用方法:
方法 | 簡介 |
---|---|
handleMessage(Message) | 對(duì)接收到的消息進(jìn)行處理 |
post(Runnable) | 讓Runnable對(duì)象入隊(duì) |
postAtTime(Runnable, long) | 指定的時(shí)間入隊(duì) |
postDelayed(Runnable, long) | 延長指定的時(shí)間入隊(duì) |
sendEmptyMessage(int) | 發(fā)送只有標(biāo)識(shí)的消息 |
sendMessage(Message) | 發(fā)送消息 |
sendMessageAtTime(Message, long) | 指定的時(shí)間發(fā)送消息 |
sendMessageDelayed(Message, long) | 延長指定的時(shí)間發(fā)送消息 |
- handleMessage方法主要用于處理消息宾肺,我們創(chuàng)建Handler時(shí)通常重寫此方法。
- post系列的方法允許我們對(duì)Runnable對(duì)象執(zhí)行入隊(duì)操作(當(dāng)它們被接收到的時(shí)候進(jìn)入MessageQueue)侵俗。
- sendMessage系列方法允許我們對(duì)Message對(duì)象執(zhí)行入隊(duì)操作锨用,Message對(duì)象包含數(shù)據(jù),這些數(shù)據(jù)將會(huì)在Handler的handleMessage(Message)方法中被處理隘谣。
五增拥、Message與MessageQueue詳解
1啄巧、Message
我們?cè)谛碌木€程里獲取到數(shù)據(jù)時(shí),首先把數(shù)據(jù)給封裝到Message里掌栅,再利用Handler來發(fā)送秩仆。Message是一個(gè)可以被發(fā)送給Handler,包含標(biāo)識(shí)和數(shù)據(jù)的對(duì)象猾封。Message對(duì)象包含兩個(gè)額外的int類型變量和一個(gè)額外的對(duì)象澄耍,利用它們大多數(shù)情況下我們不用再做內(nèi)存分配相關(guān)工作。實(shí)例化Message最好的方法是調(diào)用Message.obtain()或Handler.obtainMessage()晌缘,因?yàn)檫@兩個(gè)方法是從一個(gè)可回收利用的對(duì)象池中獲取Message的齐莲。
2、MessageQueue
MessageQueue就是Looper分發(fā)的Message集合磷箕,Message不是直接添加到MessageQueue中的选酗,而是通過與Looper關(guān)聯(lián)的Handler對(duì)象來完成的。我們可以使用方法Looper.myQueue()來獲取當(dāng)前線程的MessageQueue岳枷。
六芒填、Looper詳解
Looper,就是用于運(yùn)行一個(gè)線程的消息隊(duì)列的類嫩舟。默認(rèn)情況下線程沒有關(guān)聯(lián)的消息隊(duì)列氢烘,可以通過調(diào)用方法prepare()來創(chuàng)建隊(duì)列,然后調(diào)用loop()來不斷分發(fā)消息直到都處理完家厌。下面可以看看Looper類解析播玖。
- 首先是構(gòu)造函數(shù)。 可以看到Looper初始化時(shí)創(chuàng)建了一個(gè)消息隊(duì)列饭于,MessageQueue蜀踏,同時(shí)關(guān)聯(lián)了當(dāng)前線程。
- 之后是prepare()方法掰吕。
這里我們重點(diǎn)關(guān)注一下果覆,ThreadLocal類,它是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類殖熟,通過它可以在指定的線程中存儲(chǔ)數(shù)據(jù)局待,數(shù)據(jù)存儲(chǔ)以后,只有在指定線程中可以獲取到存儲(chǔ)的數(shù)據(jù)菱属,對(duì)于其它線程來說無法獲取到數(shù)據(jù)钳榨。
prepare方法主要是先判斷是否已經(jīng)有Looper實(shí)例,如果有就拋出錯(cuò)誤纽门,如果沒有就實(shí)例化Looper并存儲(chǔ)薛耻。
- 最后我們看下loop()函數(shù)。這個(gè)函數(shù)主要是不斷地從消息隊(duì)列中取出消息赏陵,并分發(fā)給消息關(guān)聯(lián)的Handler饼齿∷茄可以看到,方法內(nèi)部先獲取Looper實(shí)例缕溉,再獲取消息隊(duì)列考传,之后是個(gè)死循環(huán),不斷從隊(duì)列中取出消息并分發(fā)給Message的target倒淫,即Handler伙菊。
七、異步消息處理機(jī)制總結(jié)
綜合上面的內(nèi)容敌土,相信大家對(duì)整個(gè)機(jī)制已經(jīng)了解的差不多了镜硕。最后我們以Android開發(fā)藝術(shù)探索作者任玉剛的一段話作總結(jié)。
異步消息處理機(jī)制主要是指Handler的運(yùn)行機(jī)制返干,Handler的運(yùn)行需要底層的MessageQueue和Looper的支撐兴枯。
MessageQueue的中文翻譯是消息隊(duì)列,顧名思義它的內(nèi)部存儲(chǔ)了一組消息矩欠,其以隊(duì)列的形式對(duì)外提供插入和刪除的工作财剖,雖然叫做消息隊(duì)列,但是它的內(nèi)部存儲(chǔ)結(jié)構(gòu)并不是真正的隊(duì)列癌淮,而是采用單鏈表的數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)消息列表躺坟。
Looper的中文翻譯為循環(huán),在這里可以理解為消息循環(huán)乳蓄,由于MessageQueue只是一個(gè)消息的存儲(chǔ)單元咪橙,它不能去處理消息,而Looper就填補(bǔ)了這個(gè)功能虚倒,Looper會(huì)以無限循環(huán)的形式去查找是否有新消息美侦,如果有的話就處理消息,否則就一直等待著魂奥。
Looper中還有一個(gè)特殊的概念菠剩,那就是ThreadLocal,ThreadLocal并不是線程耻煤,它的作用是可以在每個(gè)線程中存儲(chǔ)數(shù)據(jù)具壮。大家知道,Handler創(chuàng)建的時(shí)候會(huì)采用當(dāng)前線程的Looper來構(gòu)造消息循環(huán)系統(tǒng)哈蝇,那么Handler內(nèi)部如何獲取到當(dāng)前線程的Looper呢嘴办?這就要使用ThreadLocal了,ThreadLocal可以在不同的線程之中互不干擾地存儲(chǔ)并提供數(shù)據(jù)买鸽,通過ThreadLocal可以輕松獲取每個(gè)線程的Looper。
當(dāng)然需要注意的是贯被,線程是默認(rèn)沒有Looper的眼五,如果需要使用Handler就必須為線程創(chuàng)建Looper妆艘。大家經(jīng)常提到的主線程,也叫UI線程看幼,它就是ActivityThread批旺,ActivityThread被創(chuàng)建時(shí)就會(huì)初始化Looper,這也是在主線程中默認(rèn)可以使用Handler的原因诵姜。