文章已授權(quán)『郭霖』公眾號發(fā)布
前言
很高興遇見你~ 歡迎閱讀我的文章。
關(guān)于Handler的博客可謂是俯拾皆是匹涮,而這也是一個老生常談的話題吁断,可見的他非吵萌铮基礎(chǔ),也非常重要仔役。但很多的博客掷伙,卻很少有從入門開始介紹,這在我一開始學(xué)習(xí)的時候就直接給我講Looper講阻塞又兵,非常難以理解任柜。同時,也很少有系統(tǒng)地講解關(guān)于Handler的一切沛厨,知識比較零散宙地。我希望寫一篇從入門到深入,系統(tǒng)地全面地講解Handler的文章逆皮,幫助大家認(rèn)識Handler宅粥。
這篇文章的講解深度循序漸進(jìn),不同程序的讀者可選擇對應(yīng)的部分查看:
- 第一部分是對于Handler的入門概述电谣。了解一個新事物秽梅,需要問三個問題:是什么、為什么剿牺、怎么用企垦。包括關(guān)于Handler的結(jié)構(gòu)等都有介紹。
- 第二部分是在對Handler有一定的認(rèn)知基礎(chǔ)上晒来,對各個類進(jìn)行詳細(xì)的講解和源碼分析钞诡。
- 第三部分是整體的流程分析以及常見問題的解析。
- 最后一部分是Android對于消息機(jī)制設(shè)計(jì)的講解以及全文總結(jié)。
文章基本涵蓋了關(guān)于Handler相關(guān)的知識臭增,因而篇幅也比較長
考慮過把文章分割成幾篇小文章,考慮到閱讀的整體性以及方便性竹习,最終還是集成了一篇大文章
文章成體系誊抛,全面地講解知識點(diǎn),而不是把知識碎片化整陌,否則很難真正去理解單一的知識拗窃,更不易于對整體知識的把握
讀者可自行選擇感興趣的章節(jié)閱讀
那么,我們開始吧泌辫。
概述
什么是Handler随夸?
準(zhǔn)確來說,是Handler機(jī)制震放,Handler只是Handler機(jī)制中的一個角色宾毒。只是我們對Handler接觸比較多,所以經(jīng)常以Handler來代稱殿遂。
Handler機(jī)制是Android中基于單線消息隊(duì)列模式的一套線程消息機(jī)制诈铛。
他的本質(zhì)是消息機(jī)制,負(fù)責(zé)消息的分發(fā)以及處理墨礁。這樣講可能有點(diǎn)抽象幢竹,不太容易理解。什么是“單線消息隊(duì)列模式”恩静?什么是“消息”焕毫?
通俗點(diǎn)來說,每個線程都有一個“流水線”驶乾,我們可往這條流水線上放“消息”邑飒,流水線的末端有工作人員會去處理這些消息。因?yàn)榱魉€是單線的级乐,所有消息都必須按照先來后到的形式依次處理(在Handler機(jī)制中有“加急線”:同步屏障幸乒,這個后面講)。如下圖:
放什么消息以及怎么處理消息唇牧,是需要我們?nèi)プ远x的罕扎。Handler機(jī)制相當(dāng)于提供了這樣的一套模式,我們只需要“放消息到流水線上”丐重,“編寫這些消息的處理邏輯”就可以了腔召,流水線會源源不斷把消息運(yùn)送到末端處理。最后注意重點(diǎn):每個線程只有一個“流水線”扮惦,他的基本范圍是線程臀蛛,負(fù)責(zé)線程內(nèi)的通信以及線程間的通信。每個線程可以看成一個廠房,每個廠房只有一個生產(chǎn)線浊仆。
兩個關(guān)鍵問題
了解Handler的作用前需要了解Handler背景下的兩個關(guān)鍵問題:
- 不能在非UI創(chuàng)建線程去操作UI
- 不能在主線程執(zhí)行耗時任務(wù)
我們普遍的認(rèn)知是:不能在非主線程更新UI客峭。但這是不準(zhǔn)確的,如果我們在子線程更新了UI抡柿,看看報錯信息是什么:
筆者留下了英語渣渣的眼淚舔琅,百度翻譯一下:
只有創(chuàng)建視圖層次結(jié)構(gòu)的原始線程才能訪問其視圖。但為什么我們一直都說是非主線程不能更新ui洲劣?這是因?yàn)槲覀兊慕缑嬉话愣际怯芍骶€程進(jìn)行繪制的备蚓,所以界面的更新也就一般都限制在主線程內(nèi)。這個異常是在viewRootIimpl.checkThread()方法中拋出來的囱稽,那可不可以繞過他郊尝?當(dāng)然可以,在他還沒創(chuàng)建出來的時候就可以偷偷更新ui了战惊。閱讀過Activity啟動流程的讀者知道流昏,ViewRootImpl是在onCreate方法之后被創(chuàng)建的,所以我們可以在onCreate方法中創(chuàng)建個子線程偷偷更新UI吞获。(Actvity啟動流程解析傳送門)但還是那句話横缔,可以,但沒必要去繞過這個限制衫哥,因?yàn)檫@是谷歌為了我們的程序更加安全而設(shè)計(jì)的茎刚。
為什么不能在子線程去更新UI?因?yàn)檫@會讓界面產(chǎn)生不可預(yù)期的結(jié)果撤逢。例如主線程在繪制一個按鈕膛锭,繪制一半另一個線程突然過來把按鈕的大小改成兩倍大,這個時候再回去主線程繼續(xù)執(zhí)行繪制邏輯蚊荣,這個繪制的效果就會出現(xiàn)問題初狰。所以UI的訪問是決不能是并發(fā)的。但互例,子線程又想更新UI奢入,怎么辦?加鎖媳叨。加鎖確實(shí)可以解決這個問題腥光,但是會帶來另外的問題:界面卡頓。鎖對于性能是有消耗的糊秆,是比較重量級的操作武福,而ui操作講究快準(zhǔn)狠,加鎖會讓ui操作性能大打折扣痘番。那有什么更好的方法捉片?Handler就是解決這個問題的平痰。
第二個問題,不能在主線程執(zhí)行耗時操作伍纫。耗時操作包括網(wǎng)絡(luò)請求宗雇、數(shù)據(jù)庫操作等等,這些操作會導(dǎo)致ANR(Application Not Responding)莹规。這個是比較好理解的赔蒲,沒有什么問題,但是這兩個問題結(jié)合起來访惜,就有大問題了。數(shù)據(jù)請求一般是耗時操作腻扇,必須在子線程進(jìn)行請求债热,而當(dāng)請求完成之后又必須更新UI,UI又只能在主線程更新幼苛,這就導(dǎo)致必須切換線程執(zhí)行代碼窒篱,上面討論了加鎖是不可取的,那么Handler的重要性就體現(xiàn)出來了舶沿。
不用Handler可不可以墙杯?可以,但沒必要括荡。Handler是谷歌設(shè)計(jì)來方便開發(fā)者切換線程以及處理消息高镐,然后你說我偏不用,我自己用Java工具類畸冲,自己弄個出來不可以嗎嫉髓?那。邑闲。算行。請收下小的膝蓋。
為什么要有Handler苫耸?
先給結(jié)論:
- 切換代碼執(zhí)行的線程
- 按順序規(guī)則地處理消息州邢,避免并發(fā)
- 阻塞線程,避免讓線程結(jié)束
- 延遲處理消息
第一個作用是最明顯也是最常用的褪子,上一部分已經(jīng)講了Handler存在的必要性量淌,android限制了不能在非UI創(chuàng)建線程去操作UI,同時不能在主線程執(zhí)行耗時任務(wù)嫌褪,所以我們一般是在子線程執(zhí)行網(wǎng)絡(luò)請求等耗時操作請求數(shù)據(jù)类少,然后再切換到主線程來更新UI。這個時候就必須用到Handler來切換線程了渔扎。上面討論過了這里不再贅述硫狞。
這里有一個誤區(qū)是:我們的activity是執(zhí)行在主線程的,我們在網(wǎng)絡(luò)請求完成之后回調(diào)主線程的方法不就切換到主線程了嗎?咳咳残吩,不要笑财忽,不要覺得這種低級錯誤太離譜,很多童鞋剛開始接觸開發(fā)的時候都會犯這個思維錯誤泣侮。這其實(shí)是理解錯了線程這個概念即彪。代碼本身并沒有限制運(yùn)行在哪個線程,代碼執(zhí)行的線程環(huán)境取決于你的執(zhí)行邏輯是在哪個線程活尊。這樣講可能還是有點(diǎn)抽象隶校。例如現(xiàn)在有一個方法void test(){}
,然后兩個不同的線程去調(diào)用它:
new Thread(){
// 第一個線程調(diào)用
test();
}.start();
new Thread(){
// 第二個線程調(diào)用
test();
}
此時雖然都是test這個方法蛹锰,但是他的執(zhí)行邏輯是由不同的線程調(diào)用的深胳,所以他是執(zhí)行在兩個不同的線程環(huán)境下。而當(dāng)我們想要把邏輯切換到另一個線程去執(zhí)行的時候铜犬,就需要用到Handler來切換邏輯舞终。
第二個作用可能看著有點(diǎn)懵。但其實(shí)他解決了另一個問題:并發(fā)操作癣猾。雖然切換線程解決了敛劝,如果主線程正在繪制一個按鈕,剛測量好按鈕的長寬纷宇,突然子線程一個新的請求過來打斷了夸盟,先停下這邊的繪制操作,把按鈕改成了兩倍大像捶,然后邏輯切回來繼續(xù)繪制满俗,這個時候之前的測量的長寬已經(jīng)是不準(zhǔn)確的了,繪制的結(jié)果肯定也不準(zhǔn)確作岖。怎么解決唆垃?單線消息隊(duì)列模型。在講什么是Handler那部分簡單介紹過痘儡,就是相當(dāng)于一個流水線一樣的模型辕万。子線程的請求會變成一個個的消息,然后主線程依次處理沉删,那么就不會出現(xiàn)繪制一半被打斷的問題了渐尿。
同時這種模型也不止用于解決ui并發(fā)問題,在ActivityThread中有一個H類矾瑰,他其實(shí)就是個Handler砖茸。在ActivityThread中定義了一百多中消息類型以及對應(yīng)的處理邏輯,這樣殴穴,當(dāng)需要讓ActivityThread處理某一個邏輯的時候凉夯,只需要發(fā)送對應(yīng)的消息給他即可货葬,而且可以保證消息按順序執(zhí)行,例如先調(diào)用onCreate再調(diào)用onResume劲够。而如果沒有Hanlder的話震桶,就需要讓ActivityThread有一百多個接口對外開放,同時還需要不斷進(jìn)行回調(diào)保證任務(wù)按順序執(zhí)行征绎。這顯然復(fù)雜了非常多蹲姐。
我們執(zhí)行一個Java程序的時候人柿,從main方法入口柴墩,執(zhí)行完成之后,馬上就退出了凫岖,但是我們android應(yīng)用程序肯定是不可以的江咳,他需要一直等待用戶的操作。而Handler機(jī)制就解決了這個問題隘截,但消息隊(duì)列中沒有任務(wù)的時候扎阶,他就會把線程阻塞汹胃,等到有新的任務(wù)的時候婶芭,再重新啟動處理消息。
第四個作用讓延遲處理消息得到了最佳解決方案着饥。假如你想讓應(yīng)用啟動5秒后界面彈出一個對話框犀农,沒有handler的情況下,會如何處理宰掉?開一個Thread然后使用Thread.sleep讓線程睡眠一對應(yīng)的時間對吧呵哨,但如果多個延遲任務(wù)呢?而開啟線程也是個比較重量級的操作且線程的數(shù)量有限轨奄。而可以直接給Handler發(fā)送延遲對應(yīng)時間的消息孟害,他會在對應(yīng)時間之后準(zhǔn)時處理該消息(當(dāng)然有特殊情況,如單件消息處理時間過長或者同步屏障挪拟,后面會講到)挨务。而且無論發(fā)送多少延遲消息都不會對性能有任何影響。同時玉组,也是通過這個功能來記錄ANR的時間谎柄。
講這些作用可能讀者心中并沒有一個很形象的概念,也可能看完就忘了惯雳。但是關(guān)于Handler的定義不能忘:Handler機(jī)制是Android中基于單線消息隊(duì)列模式的一套線程消息機(jī)制朝巫。,上述四個作用是為了讓讀者更好地理解Handler機(jī)制石景。
如何使用Handler
我們平常使用Handler有兩種不同的創(chuàng)建方式劈猿,但總體流程是相同的:
- 創(chuàng)建Looper
- 使用Looper創(chuàng)建Handler
- 啟動Looper
- 使用Handler發(fā)送信息
Looper可理解為循環(huán)器拙吉,就像“流水線”上的滾帶,后面會詳細(xì)講到糙臼。每個線程只有一個Looper庐镐,通常主線程已經(jīng)創(chuàng)建好了,追溯應(yīng)用程序啟動流程可以知道啟動過程中調(diào)用了Looper.prepareMainLooper变逃,而在子線程就必須使用如下方法來初始化Looper:
Looper.prepare();
第二步是創(chuàng)建Handler必逆,也是最熟悉的一步。我們有兩種方法來創(chuàng)建Handler:傳入callBack對象和繼承揽乱。如下:
public class MainActivity extends AppComposeActivity{
...;
// 第一種方法:使用callBack創(chuàng)建handler
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Handler handler = Handler(Looper.myLooper(),new CallBack(){
public Boolean handleMessage(Message msg) {
TODO("Not yet implemented")
}
});
}
// 第二種方法:繼承Handler并重寫handlerMessage方法
static MyHandler extends Hanlder{
public MyHandler(Looper looper){
super(looper);
}
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
// TODO(重寫這個方法)
}
}
}
注意第二種方法名眉,要使用靜態(tài)內(nèi)部類,不然可能會造成內(nèi)存泄露凰棉。原因是非靜態(tài)內(nèi)部類會持有外部類的引用损拢,而Handler發(fā)出的Message會持有Handler的引用。如果這個Message是個延遲的消息撒犀,此時activity被退出了福压,但Message依然在“流水線”上,Message->handler->activity或舞,那么activity就無法被回收荆姆,導(dǎo)致內(nèi)存泄露。
兩種Handler的寫法各有千秋映凳,繼承法可以寫比較復(fù)雜的邏輯胆筒,callback法適合比價簡單的邏輯,看具體的業(yè)務(wù)來選擇诈豌。
然后再調(diào)用Looper的loope方法來啟動Looper:
Looper.loop();
最后就是使用Handler來發(fā)送信息了仆救。當(dāng)我們獲得handler的實(shí)例之后,就可以通過他的sendMessage相方法和post相關(guān)方法來發(fā)送信息矫渔,如下:
handler.sendMessage(msg);
handler.sendMessageDelayed(msg,delayTime);
handler.post(runnable);
handler.postDelayed(runnable,delayTime);
然后一般情況下是哪個Handler發(fā)出的信息彤蔽,最終由哪個Handler來處理。這樣庙洼,只要我們拿到Handler對象顿痪,就可以往對應(yīng)的線程發(fā)送信息了。
Handler內(nèi)部模式結(jié)構(gòu)
經(jīng)過前面的介紹對于Looper已經(jīng)有了一定的認(rèn)知送膳,但可能對他內(nèi)部的模式還不太清楚员魏。這一部分先講解Handler的大概內(nèi)部模式,目的是為下面的詳解做鋪墊叠聋,為做整體概念感知撕阎。先上圖:
Handler機(jī)制內(nèi)部有三大關(guān)鍵角色:Handler,Looper碌补,MessageQueue虏束。其中MessageQueue是Looper內(nèi)部的一個對象棉饶,MessageQueue和Looper每個線程有且只有一個,而Handler是可以有很多個的镇匀。他們的工作流程是:
- 用戶使用線程的Looper構(gòu)建Handler之后照藻,通過Handler的send和post方法發(fā)送消息
- 消息會加入到MessageQueue中,等待Looper獲取處理
- Looper會不斷地從MessageQueue中獲取Message然后交付給對應(yīng)的Handler處理
這就是大名鼎鼎的Handler機(jī)制內(nèi)部模式了汗侵,說難幸缕,其實(shí)也是很簡單汤善。
Handler機(jī)制關(guān)鍵類
一次氨、ThreadLocal
概述
ThreadLocal是Java中一個用于線程內(nèi)部存儲數(shù)據(jù)的工具類俊卤。
ThreadLocal是用來存儲數(shù)據(jù)的渗常,但是每個線程只能訪問到各自線程的數(shù)據(jù)。我們一般的用法是:
ThreadLocal<String> stringLocal = new ThreadLocal<>();
stringLocal.set("java");
String s = stringLocal.get();
不同的線程之間訪問到的數(shù)據(jù)是不一樣的:
public static void main(String[] args){
ThreadLocal<String> stringLocal = new ThreadLocal<>();
stringLocal.set("java");
System.out.println(stringLocal.get());
new Thread(){
System.out.println(stringLocal.get());
}
}
結(jié)果:
java
null
線程只能訪問到自己線程存儲的數(shù)據(jù)绰寞。
ThreadLocal的作用
ThreadLocal的特性適用于同樣的數(shù)據(jù)類型归苍,不同的線程有不同的備份情況拥峦,如我們這篇文章一直在講的Looper只恨。每個線程都有一個對象译仗,但是不同線程的Looper是不一樣的,這個時候就特別適合使用ThreadLocal來存儲數(shù)據(jù)官觅,這也是為什么這里要講ThreadLocal的原因
ThreadLocal內(nèi)部結(jié)構(gòu)
ThreadLocal的內(nèi)部機(jī)制結(jié)構(gòu)如下:
每個Thread纵菌,也就是每個線程內(nèi)部維護(hù)有一個ThreadLocalMap,ThreadLocalMap內(nèi)部存儲多個Entry缰猴。Entry可以理解為鍵值對产艾,他的本質(zhì)是一個弱引用疤剑,內(nèi)部有一個object類型的內(nèi)部變量滑绒,如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry是ThreadLocalMap的一個靜態(tài)內(nèi)部類,這樣每個Entry里面就維護(hù)了一個ThreadLocal和ThreadLocal泛型對象隘膘。每個線程的內(nèi)部維護(hù)有一個Entry數(shù)組疑故,并通過hash算法使得讀取數(shù)據(jù)的速度達(dá)到O(1)。由于不同的線程對應(yīng)的Thread對象不同弯菊,所以對應(yīng)的ThreadLocalMap肯定也不同纵势,這樣只有獲取到Thread對象才能獲取到其內(nèi)部的數(shù)據(jù),數(shù)據(jù)就被隔離在不同的線程內(nèi)部了管钳。
ThreadLocal工作流程
那ThreadLocal是怎么實(shí)現(xiàn)把數(shù)據(jù)存儲在不同線程中的钦铁?先從他的set方法入手:
TheadLocal.class
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
邏輯不是很復(fù)雜,首先獲取當(dāng)前線程的Thread對象才漆,然后再獲取Thread的ThreadLocalMap對象牛曹,如果該map對象不存在則創(chuàng)建一個并調(diào)用他的set方法把數(shù)據(jù)存儲起來。我們繼續(xù)看ThreadLocalMap的set方法:
ThreadLocalMap.class
private void set(ThreadLocal<?> key, Object value) {
// 每個ThreadLocalMap內(nèi)部都有一個Entry數(shù)組
Entry[] tab = table;
int len = tab.length;
// 獲取新的ThreadLocal在Entry數(shù)組中的下標(biāo)
int i = key.threadLocalHashCode & (len-1);
// 判斷當(dāng)前位置是否發(fā)生了Hash沖突
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 如果數(shù)據(jù)存在且相同則直接返回
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 若當(dāng)前位置沒有其他元素則直接把新的Entry對象放入
tab[i] = new Entry(key, value);
int sz = ++size;
// 判斷是否需要對數(shù)組進(jìn)行擴(kuò)容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
這里的邏輯和HashMap是很像的醇滥,我們可以直接使用HashMap的思維來理解ThreadLocalMap:ThreadLocalMap的key是ThreadLocal黎比,value是ThreadLocal對應(yīng)的泛型超营。他的存儲步驟如下:
- 根據(jù)自身的threadLocalHashCode與數(shù)組的長度進(jìn)行相與得到下標(biāo)
- 如果此下標(biāo)為空,則直接插入
- 如果此下標(biāo)已經(jīng)有元素阅虫,則判斷兩者的ThreadLocal是否相同演闭,相同則更新value后返回,否則找下一個下標(biāo)
- 直到找到合適的位置把entry對象插入
- 最后判斷是否需要對entry數(shù)組進(jìn)行擴(kuò)容
是不是和HashMap非常像颓帝?和HashMap的不同是:hash算法不一樣米碰,以及這里使用的是開發(fā)地址法,而HashMap使用的是鏈表法购城。ThreadLocalMap犧牲一定的空間來換取更快的速度见间。具體的Hash算法這里就不再深入了,有興趣的讀者可以閱讀這篇文章ThreadLocal傳送門
然后繼續(xù)看ThreadLocal的get方法:
ThreadLocal.class
public T get() {
// 獲取當(dāng)前線程的ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 根據(jù)ThreadLocal獲取Entry對象
ThreadLocalMap.Entry e = map.getEntry(this);
// 如果沒找到也會執(zhí)行初始化工作
if (e != null) {
@SuppressWarnings("unchecked")
// 把獲取到的對象進(jìn)行返回
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
前面講到ThreadLocalMap其實(shí)非常像一個HashMap工猜,他的get方法也是一樣的米诉。使用ThreadLocal作為key獲取到對應(yīng)的Entry,再把value返回即可篷帅。如果map尚未初始化則會執(zhí)行初始化操作史侣。下面繼續(xù)看下ThreadLocalMap的get方法:
ThreadLocalMap.class
private Entry getEntry(ThreadLocal<?> key) {
// 根據(jù)hash算法找到下標(biāo)
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 找到數(shù)據(jù)則返回,否則通過開發(fā)地址法尋找下一個下標(biāo)
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
利用ThreadLocal的threadLocalHashCode得到下標(biāo)魏身,然后根據(jù)下標(biāo)找到數(shù)據(jù)惊橱。沒找到則根據(jù)算法尋找下個下標(biāo)。
內(nèi)存泄露問題
我們會發(fā)現(xiàn)Entry中箭昵,ThreadLocal是一個弱引用税朴,而value則是強(qiáng)引用。如果外部沒有對ThreadLocal的任何引用家制,那么ThreadLocal就會被回收正林,此時其對應(yīng)的value也就變得沒有意義了,但是卻無法被回收颤殴,這就造成了內(nèi)存泄露觅廓。怎么解決?在ThreadLocal回收的時候記得調(diào)用其remove方法把entry移除涵但,防止內(nèi)存泄露杈绸。
ThreadLocal總結(jié)
ThreadLocal適合用于在不同線程作用域的數(shù)據(jù)備份
ThreadLocal機(jī)制通過在每個線程維護(hù)一個ThreadLocalMap,其key為ThreadLocal矮瘟,value為ThreadLocal對應(yīng)的泛型對象瞳脓,這樣每個ThreadLocal就可以作為key將不同的value存儲在不同Thread的Map中,當(dāng)獲取數(shù)據(jù)的時候澈侠,同個ThreadLocal就可以在不同線程的Map中得到不同的數(shù)據(jù)劫侧,如下圖:
ThreadLocalMap類似于一個改版的HashMap,內(nèi)部也是使用數(shù)組和Hash算法來存儲數(shù)據(jù)埋涧,使得存儲和讀取的速度非嘲辶桑快奇瘦。
同時使用ThreadLocal需要注意內(nèi)存泄露問題,當(dāng)ThreadLocal不再使用的時候劲弦,需要通過remove方法把value移除耳标。
二、Message
概述
Message是負(fù)責(zé)承載消息的類邑跪,主要是關(guān)注他的內(nèi)部屬性:
// 用戶自定義次坡,主要用于辨別Message的類型
public int what;
// 用于存儲一些整型數(shù)據(jù)
public int arg1;
public int arg2;
// 可放入一個可序列化對象
public Object obj;
// Bundle數(shù)據(jù)
Bundle data;
// Message處理的時間。相對于1970.1.1而言的時間
// 對用戶不可見
public long when;
// 處理這個Message的Handler
// 對用戶不可見
Handler target;
// 當(dāng)我們使用Handler的post方法時候就是把runnable對象封裝成Message
// 對用戶不可見
Runnable callback;
// MessageQueue是一個鏈表画畅,next表示下一個
// 對用戶不可見
Message next;
循環(huán)利用Message
當(dāng)我們獲取Message的時候砸琅,官方建議是通過Message.obtain()方法來獲取,當(dāng)使用完之后使用recycle()方法來回收循環(huán)利用轴踱。而不是直接new一個新的對象:
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
Message維護(hù)了一個靜態(tài)鏈表症脂,鏈表頭是sPool
,Message有一個next屬性,Message本身就是鏈表結(jié)構(gòu)淫僻。sPoolSync
是一個object對象诱篷,僅作為解決并發(fā)訪問安全設(shè)計(jì)。當(dāng)我們調(diào)用obtain來獲取一個新的Message的時候雳灵,首先會檢查鏈表中是否有空閑的Message棕所,如果沒有則新建一個返回。
當(dāng)我們使用完成之后悯辙,可以調(diào)用Message的recycle方法進(jìn)行回收:
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
如果這個Message正在使用則會拋出異常琳省,否則則調(diào)用recycleUnchecked
進(jìn)行回收:
void recycleUnchecked() {
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
這個方法的邏輯也非常簡單,把Message中的內(nèi)容清空躲撰,然后判斷鏈表是否達(dá)到最大值(50)针贬,然后插入鏈表中。
Message總結(jié)
Message的作用就是承載消息茴肥,他的內(nèi)部有很多的屬性用于給用戶賦值坚踩。同時Message本身也是一個鏈表結(jié)構(gòu)荡灾,無論是在MessageQueue還是在Message內(nèi)部的回收機(jī)制瓤狐,都是使用這個結(jié)構(gòu)來形成鏈表。同時官方建議不要直接初始化Message批幌,而是通過Message.obtain()方法來獲取一個Message循環(huán)利用础锐。一般來說我們不需要去調(diào)用recycle進(jìn)行回收,在Looper中會自動把Message進(jìn)行回收荧缘,后面會講到皆警。
三、MessageQueue
概述
每個線程都有且只有一個MessageQueue截粗,他是一個用于承載消息的隊(duì)列信姓,內(nèi)部使用鏈表作為數(shù)據(jù)結(jié)構(gòu)鸵隧,所以待處理的消息都會在這里排隊(duì)。前面講到ThreadLocalMap是一個“修改版的HashMap”意推,而MessageQueue就是一個“修改版的LinkQueue”豆瘫。他也有兩個關(guān)鍵的方法:入隊(duì)(enqueueMessage)和出隊(duì)(next)。這也是MessageQueue的重點(diǎn)所在菊值。
Message還涉及到一個關(guān)鍵概念:線程休眠外驱。當(dāng)MessageQueue中沒有消息或者都在等待中,則會將線程休眠腻窒,讓出cpu資源昵宇,提高cpu的利用效率。進(jìn)入休眠后儿子,如果需要繼續(xù)執(zhí)行代碼則需要將線程喚醒瓦哎。當(dāng)方法暫時無法直接返回需要等待的時候,則可以將線程阻塞柔逼,即休眠杭煎,等待被喚醒繼續(xù)執(zhí)行邏輯。這部分內(nèi)容也會在后面詳細(xì)講卒落。
關(guān)鍵方法
-
出隊(duì) -- next()
next方法主要是做消息出隊(duì)工作羡铲。
Message next() { // 如果looper已經(jīng)退出了,這里就返回null final long ptr = mPtr; if (ptr == 0) { return null; } ... // 阻塞時間 int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } // 阻塞對應(yīng)時間 nativePollOnce(ptr, nextPollTimeoutMillis); // 對MessageQueue進(jìn)行加鎖儡毕,保證線程安全 synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; ... if (msg != null) { if (now < msg.when) { // 下一個消息還沒開始也切,等待兩者的時間差 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 獲得消息且現(xiàn)在要執(zhí)行,標(biāo)記MessageQueue為非阻塞 mBlocked = false; // 鏈表操作 if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; msg.markInUse(); return msg; } } else { // 沒有消息腰湾,進(jìn)入阻塞狀態(tài) nextPollTimeoutMillis = -1; } ... } }
代碼很長雷恃,其中還涉及了同步屏障和IdleHandler,這兩部分內(nèi)容我放在后面講费坊,這里先講主要的出隊(duì)邏輯倒槐。代碼中我都加了注釋,這里還是再講一下附井。next方法目的是獲取MessageQueue中的一個Message讨越,如果隊(duì)列中沒有消息的話,就會把方法阻塞住永毅,等待新的消息來喚醒把跨。主要步驟如下:
- 如果Looper已經(jīng)退出了,直接返回null
- 進(jìn)入死循環(huán)沼死,直到獲取到Message或者退出
- 循環(huán)中先判斷是否需要進(jìn)行阻塞着逐,阻塞結(jié)束后,對MessageQueue進(jìn)行加鎖,獲取Message
- 如果MessageQueue中沒有消息耸别,則直接把線程無限阻塞等待喚醒健芭;
- 如果MessageQueue中有消息,則判斷是否需要等待秀姐,否則則直接返回對應(yīng)的message吟榴。
可以看到邏輯就是判斷當(dāng)前時間Message中是否需要等待。其中
nextPollTimeoutMillis
表示阻塞的時間囊扳,-1
表示無限時間吩翻,只有通過喚醒才能打破阻塞。 -
入隊(duì) -- enqueueMessage()
MessageQueue.class boolean enqueueMessage(Message msg, long when) { // Hanlder不允許為空 if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } // 對MessageQueue進(jìn)行加鎖 synchronized (this) { // 判斷目標(biāo)thread是否已經(jīng)死亡 if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } // 標(biāo)記Message正在被執(zhí)行锥咸,以及需要被執(zhí)行的時間狭瞎,這里的when是距離1970.1.1的時間 msg.markInUse(); msg.when = when; // p是MessageQueue的鏈表頭 Message p = mMessages; boolean needWake; // 判斷是否需要喚醒MessageQueue // 如果有新的隊(duì)頭,同時MessageQueue處于阻塞狀態(tài)則需要喚醒隊(duì)列 if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { ... // 根據(jù)時間找到插入的位置 Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } ... } msg.next = p; prev.next = msg; } // 如果需要則喚醒隊(duì)列 if (needWake) { nativeWake(mPtr); } } return true; }
這部分的代碼好像也很多搏予,但是邏輯也是不復(fù)雜熊锭,主要就是鏈表操作以及判斷是否需要喚醒MessageQueue,代碼中我加了一些注釋雪侥,下面再總結(jié)一下:
首先判斷message的目標(biāo)handler不能為空且不能正在使用中
對MessageQueue進(jìn)行加鎖
判斷目標(biāo)線程是否已經(jīng)死亡碗殷,死亡則直接返回false
初始化Message的執(zhí)行時間以及標(biāo)記正在執(zhí)行中
然后根據(jù)Message的執(zhí)行時間,找到在鏈表中的插入位置進(jìn)行插入
同時判斷是否需要喚醒MessageQueue速缨。有兩種情況需要喚醒:當(dāng)新插入的Message在鏈表頭時锌妻,如果messageQueue是空的或者正在等待下個任務(wù)的延遲時間執(zhí)行,這個時候就需要喚醒MessageQueue旬牲。
MessageQueue總結(jié)
Message兩大重點(diǎn):阻塞休眠和隊(duì)列操作仿粹。基本都是圍繞著兩點(diǎn)來展開原茅。而源碼中還涉及到了同步屏障以及IdleHandler吭历,這兩部分內(nèi)容我分開到了最后一部分的相關(guān)問題中講。平時用的比較少擂橘,但也是比較重要的內(nèi)容晌区。
四、Looper
概述
Looper可以說是Handler機(jī)制中的一個非常重要的核心通贞。Looper相當(dāng)于線程消息機(jī)制的引擎朗若,驅(qū)動整個消息機(jī)制運(yùn)行。Looper負(fù)責(zé)從隊(duì)列中取出消息滑频,然后交給對應(yīng)的Handler去處理捡偏。如果隊(duì)列中沒有消息,則MessageQueue的next方法會阻塞線程峡迷,等待新的消息的到來。每個線程有且只能有一個“引擎”,也就是Looper绘搞,如果沒有Looper彤避,那么消息機(jī)制就運(yùn)行不起來,而如果有多個Looper夯辖,則會違背單線操作的概念琉预,造成并發(fā)操作。
每個線程僅有一個Looper蒿褂,由不同Looper分發(fā)的Message運(yùn)行在不同的線程中圆米。Looper的內(nèi)部維護(hù)一個MessageQueue,當(dāng)初始化Looper的時候會順帶初始化MessageQueue啄栓。
Looper使用ThreadLocal來保證每個線程都有且只有一個相同的副本娄帖。
關(guān)鍵方法
-
prepare : 初始化Looper
Looper.class static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); public static void prepare() { prepare(true); } // 最終調(diào)用到了這個方法 private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
每個線程使用Handler之前,都必須調(diào)用
Looper.prepare()
方法來初始化當(dāng)前線程的Looper昙楚。參數(shù)quitAllowed
表示該Looper是否可以退出近速。主線程的Looper是不能退出的,不然程序就直接終止了堪旧。我們在主線程使用Handler的時候是不用初始化Looper的削葱,為什么?因?yàn)锳ctiviy在啟動的時候就已經(jīng)幫我們初始化主線程Looper了淳梦,這點(diǎn)在后面再詳細(xì)展開析砸。所以在主線程我們可以直接調(diào)用Looper.myLooper()
獲取當(dāng)前線程的Looper。prepare方法重點(diǎn)在
sThreadLocal.set(new Looper(quitAllowed));
爆袍,可以看出來這里使用了ThreadLocal來創(chuàng)建當(dāng)前線程的Looper對象副本干厚。如果當(dāng)前線程已經(jīng)有Looper了,則會拋出異常螃宙。sThreadLocal是Looper類的靜態(tài)變量蛮瞄,前面我們介紹過了ThreadLocal了,這里每個線程調(diào)用一次prepare方法就可以初始化當(dāng)前線程的Looper了谆扎。接下來再看到Looper的構(gòu)造方法:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
邏輯很簡單挂捅,初始化了一個MessageQueue,再把當(dāng)前的線程的Thread對象賦值給mThread堂湖。
-
myLooper() : 獲取當(dāng)前線程的Looper對象
獲取當(dāng)前線程的Looper對象闲先。這個方法就是直接調(diào)用ThreadLocal的get方法:
public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
-
loop() : 循環(huán)獲取消息
當(dāng)Looper初始化完成之后,他是不會自己啟動的无蜂,需要我們自己去啟動Looper伺糠,調(diào)用Looper的
loop()
方法即可:public static void loop() { // 獲取當(dāng)前線程的Looper final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; ... for (;;) { // 獲取消息隊(duì)列中的消息 Message msg = queue.next(); // might block if (msg == null) { // 返回null說明MessageQueue退出了 return; } ... try { // 調(diào)用Message對應(yīng)的Handler處理消息 msg.target.dispatchMessage(msg); if (observer != null) { observer.messageDispatched(token, msg); } dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } ... // 回收Message msg.recycleUnchecked(); } }
loop()方法就是Looper這個“引擎”的核心所在。首先獲取當(dāng)前線程的Looper對象斥季,沒有則拋出異常训桶。然后進(jìn)入一個死循環(huán):不斷調(diào)用MessageQueue的next方法來獲取消息累驮,然后調(diào)用message的目標(biāo)handler的dispatchMessage方法來處理Message。
前面我們了解過了MessageQueue舵揭,next方法是可能會進(jìn)行阻塞的:當(dāng)MessageQueue為空或者目前沒有任何消息需要處理谤专。所以Looper就會一直等待,阻塞在里午绳,線程也就不會結(jié)束置侍。當(dāng)我們退出Looper的時候,next方法會返回null拦焚,那么Looper也就會跟著結(jié)束了蜡坊。
同時,因?yàn)長ooper是運(yùn)行在不同線程的邏輯赎败,其調(diào)用的dispatchMessage方法也是運(yùn)行在不同的線程秕衙,這就達(dá)到了切換線程的目的。
-
quit/quitSafely : 退出Looper
quit是直接將Looper退出螟够,quitSafely是將MessageQueue中的不需要等待的消息處理完成之后再退出灾梦,看一下代碼:
public void quit() { mQueue.quit(false); } // 最終都是調(diào)用到了這個方法 void quit(boolean safe) { // 如果不能退出則拋出異常。這個值在初始化Looper的時候被賦值 if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { // 退出一次之后就無法再次運(yùn)行了 if (mQuitting) { return; } mQuitting = true; // 執(zhí)行不同的方法 if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } // 喚醒MessageQueue nativeWake(mPtr); } }
我們可以發(fā)現(xiàn)最后都調(diào)用了quitSafely方法妓笙。這個方法先判斷是否能退出若河,然后再執(zhí)行退出邏輯。如果mQuitting==true寞宫,那么這里會直接方法萧福,我們會發(fā)現(xiàn)mQuitting這個變量只有在這里被執(zhí)行了賦值,所以一旦looper退出辈赋,則無法再次運(yùn)行了鲫忍。之后執(zhí)行不同的退出邏輯,我們分別看一下:
private void removeAllMessagesLocked() { Message p = mMessages; while (p != null) { Message n = p.next; p.recycleUnchecked(); p = n; } mMessages = null; }
這個方法很簡單钥屈,直接把當(dāng)前所有的Message全部移除悟民。再看一下另一個方法:
private void removeAllFutureMessagesLocked() { final long now = SystemClock.uptimeMillis(); Message p = mMessages; if (p != null) { // 如果都在等待,則全部移除篷就,直接退出 if (p.when > now) { removeAllMessagesLocked(); } else { Message n; // 把需要等待的Message全部移除 for (;;) { n = p.next; if (n == null) { return; } if (n.when > now) { break; } p = n; } p.next = null; do { p = n; n = p.next; p.recycleUnchecked(); } while (n != null); } } }
這個方法邏輯也不復(fù)雜射亏,就是把需要等待的Message全部移除,當(dāng)前需要執(zhí)行的Message則保留竭业。最終在MessageQueue的next方法中智润,會進(jìn)行判斷后返回null,表示退出未辆,Looper收到這個返回值之后也跟著退出了窟绷。
Looper總結(jié)
Looper作為Handler消息機(jī)制的“動力引擎”,不斷從MessageQueue中獲取消息咐柜,然后交給Handler去處理兼蜈。Looper的使用前需要先初始化當(dāng)前線程的Looper對象攘残,再調(diào)用loop方法來啟動它。
同時Handler也是實(shí)現(xiàn)切換的核心饭尝,因?yàn)椴煌腖ooper運(yùn)行在不同的線程肯腕,他所調(diào)用的dispatchMessage方法則運(yùn)行在不同的線程献宫,所以Message的處理就被切換到Looper所在的線程了钥平。當(dāng)looper不再使用時,可調(diào)用不同的退出方法來退出他姊途,注意Looper一旦退出涉瘾,線程則會直接結(jié)束。
五捷兰、Handler
概述
我們整個消息機(jī)制稱為Handler機(jī)制就可以知道Handler我們的使用頻率之高立叛,一般情況下我們的使用也是圍繞著Handler來展開。Handler是作為整個消息機(jī)制的消息發(fā)起者與處理者贡茅,消息在不同的線程通過Handler發(fā)送到目標(biāo)線程的MessageQueue中秘蛇,然后目標(biāo)線程的Looper再調(diào)用Handler的dispatchMessage方法來處理消息。
創(chuàng)建Handler
一般情況下我們使用Handler有兩種方式: 繼承Handler并重寫handleMessage方法顶考,直接創(chuàng)建Handler對象并傳入callBack赁还,這在前面使用Handler部分講過就不再贅述。
需要注意的一點(diǎn)是:創(chuàng)建Handler必須顯示指明Looper參數(shù)驹沿,而不能直接使用無參構(gòu)造函數(shù)艘策,如:
Handler handler = new Handler(); //1
Handler handler = new Handler(Looper.myLooper())//2
1是錯的,2是對的渊季。避免在Handler創(chuàng)建過程中Looper已經(jīng)退出的情況朋蔫。
發(fā)送消息
Handler發(fā)送消息有兩種系列方法 : postxx 和 sendxx。如下:
public final boolean post(@NonNull Runnable r);
public final boolean postDelayed(@NonNull Runnable r, long delayMillis);
public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis);
public final boolean postAtFrontOfQueue(@NonNull Runnable r);
public final boolean sendMessage(@NonNull Message msg);
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis);
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis);
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg)
這里我只列出了比較常用的兩類方法却汉。除了插在隊(duì)列頭的兩個方法驯妄,其他方法最終都調(diào)用到了sendMessageAtTime
。我們從post方法跟源碼分析一下:
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
post方法把runnable對象封裝成一個Message合砂,再調(diào)用sendMessageDelayed
方法青扔,我們看看他是如何封裝的:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
可以看到邏輯很簡單,把runnable對象直接賦值給callBack屬性既穆。接下來回去繼續(xù)看sendMessageDelayed
:
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
sendMessageDelayed
把小于0的延遲時間改成0赎懦,然后調(diào)用sendMessageAtTime
。這個方法主要是判斷MessageQueue是否已經(jīng)初始化了幻工,然后再調(diào)用enqueueMessage
方法進(jìn)行入隊(duì)操作:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// 這里把target設(shè)置成自己
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// 異步handler設(shè)置標(biāo)志位true励两,后面會講到同步屏障
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 最后調(diào)用MessageQueue的方法入隊(duì)
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到Handler的入隊(duì)操作也是很簡單,把Message的target設(shè)置成本身囊颅,這樣這個Message最后就是由自己來處理当悔。最后調(diào)用MessageQueue的入隊(duì)方法來入隊(duì)傅瞻,這在前面講過就不再贅述。
其他的發(fā)送消息方法都是大同小異盲憎,讀者感興趣可以自己去跟蹤一下源碼嗅骄。
處理消息
上面講Looper處理消息的時候,最后就是調(diào)用handler的dispatchMessage方法來處理饼疙。我們來看一下這個方法:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
他的邏輯也不復(fù)雜溺森。首先判斷Message是否有callBack,有的話就直接執(zhí)行callBack的邏輯窑眯,這個callBack就是我們調(diào)用handler的post系列方法傳進(jìn)去的Runnable對象屏积。否則判斷Handler是否有callBack,有的話執(zhí)行他的方法磅甩,如果返回true則結(jié)束炊林,如果返回false則直接Handler本身的handleMessage方法。這個過程可以用下面的圖表示一下:
內(nèi)存泄露問題
當(dāng)我們使用繼承Handler方法來使用Handler的時候卷要,要注意使用靜態(tài)內(nèi)部類渣聚,而不要用非靜態(tài)內(nèi)部類。因?yàn)榉庆o態(tài)內(nèi)部類會持有外部類的引用僧叉,而從上面的分析我們知道Message在被入隊(duì)之后他的target屬性是指向了Handler奕枝,如果這個Message是一個延遲的消息,那么這一條引用鏈的對象就遲遲無法被釋放彪标,造成內(nèi)存泄露倍权。
一般這種泄露現(xiàn)象在于:我們在Activity中發(fā)送了一個延遲消息,然后退出了activity捞烟,但是由于無法釋放薄声,這樣activity就無法被回收,造成內(nèi)存泄露题画。
Handler總結(jié)
Handler作為消息的處理和發(fā)送者默辨,是整個消息機(jī)制的起點(diǎn)和終點(diǎn),也是我們接觸最多的一個類苍息,因?yàn)槲覀兎Q此消息機(jī)制為Handler機(jī)制缩幸。Handler最重要的就是發(fā)送和處理消息,只要熟練掌握這兩方面的內(nèi)容就可以了竞思。同時注意內(nèi)存泄露問題表谊,不要使用非靜態(tài)內(nèi)部類去繼承Handler。
六盖喷、HandlerThread
概述
有時候我們需要開辟一個線程來執(zhí)行一些耗時的任務(wù)爆办。一般情況下可以通過新建一個Thread,然后再在他的run方法里初始化該線程的Looper课梳,這樣就可以用他的Looper來切線程處理消息了距辆。如下(這里是kotlin代碼余佃,和java差不多相信可以看得懂的):
val thread = object : Thread(){
lateinit var mHandler: Handler
override fun run() {
super.run()
Looper.prepare()
mHandler = Handler(Looper.myLooper()!!)
Looper.loop()
}
}
thread.start()
thread.mHandler.sendMessage(Message.obtain())
但是,運(yùn)行一下跨算,炸了:
Handler還未初始化爆土。Looper初始化是需要一定的時間,就導(dǎo)致了這個問題诸蚕,那簡單步势,等待一下就可以了,上代碼:
val thread = object : Thread(){
lateinit var mHandler: Handler
override fun run() {
super.run()
Looper.prepare()
mHandler = Handler(Looper.myLooper()!!)
Looper.loop()
}
}
thread.start()
Thread(){
Thread.sleep(10000)
thread.mHandler.sendMessage(Message.obtain())
}.start()
執(zhí)行一下挫望,誒碴裙,沒有報錯了果然可以磁椒。但是!6灰铩泉哈! 蛉幸,這樣的代碼顯得特別的難堪和臃腫,還要再開啟一個線程來延遲處理丛晦。那有沒有更好的解決方案奕纫?有,HandlerThread烫沙。
HandlerThread本身是一個Thread匹层,他繼承自Thread,他的代碼并不復(fù)雜锌蓄,看一下(代碼還是有點(diǎn)多升筏,可以選擇看或者不看,我下面會講重點(diǎn)方法):
public class HandlerThread extends Thread {
// 依次是:線程優(yōu)先級瘸爽、線程id您访、線程looper、以及內(nèi)部handler
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;
// 兩個構(gòu)造器剪决。name是線程名字灵汪,priority是線程優(yōu)先級
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
// 在Looper開始運(yùn)行前的方法
protected void onLooperPrepared() {
}
// 初始化Looper
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
// 通知初始化完成
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
// 獲取當(dāng)前線程的Looper
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// 如果尚未初始化則會一直阻塞知道初始化完成
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
// 利用Object對象的wait方法
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
// 獲取handler,該方法被標(biāo)記為hide柑潦,用戶無法獲取
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
// 兩種不同類型的退出享言,前面講過不再贅述
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
// 獲取線程id
public int getThreadId() {
return mTid;
}
}
整個類的代碼不是很多,重點(diǎn)在run()
和getLooper()
方法渗鬼。首先看到getLooper方法:
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// 如果尚未初始化則會一直阻塞知道初始化完成
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
// 利用Object對象的wait方法
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
和我們前面自己寫的不同览露,他有一個wait()
,這個是Java中Object類提供的一個方法乍钻,類似于我們前面講的MessageQueue阻塞肛循。等到Looper初始化完成之后就會喚醒他铭腕,就可以順利返回了,不會造成Looper尚未初始化完成的情況多糠。然后再看到run方法:
// 初始化Looper
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
// 通知初始化完成
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
常規(guī)的Looper初始化累舷,完成之后調(diào)用了notifyAll()
方法進(jìn)行喚醒,對應(yīng)了上面的getLooper方法夹孔。
HandlerThread的使用
HandlerThread的使用范圍很有限被盈,開個子線程不斷接受消息處理耗時任務(wù)。所以他的使用方法也是比較固定:
HandlerThread ht = new HandlerThread("handler");
Handler handler = new Hander(ht.getLooper());
handler.sendMessage(msg);
獲取到他的Looper搭伤,外部自定義Handler來使用即可只怎。
七、總結(jié)
Handler怜俐,MessageQueue身堡,Looper三者共同構(gòu)成了android消息機(jī)制,各司其職拍鲤。其中Handler主要負(fù)責(zé)發(fā)送和處理消息贴谎,MessageQueue主要負(fù)責(zé)消息的排序以及在沒有需要處理的消息的時候阻塞代碼,Looper負(fù)責(zé)從MessageQueue中取出消息給Handler處理季稳,同時達(dá)到切換線程的目的擅这。通過源碼分析,希望讀者可以對這些概念有更加清晰的認(rèn)知景鼠。
工作流程
這一部分主要講整體的流程仲翎,前面零零散散講了各個組件的功能以及源碼,現(xiàn)在就統(tǒng)一來講一下他們的整體流程铛漓。先看圖:
- Handler設(shè)置一系列的api供給開發(fā)者可以使用Handler發(fā)送各種類型的信息溯香,最終都調(diào)用到了enqueueMessage方法來入隊(duì)
- 調(diào)用MessageQueue的enqueueMessage方法把消息插入到MessageQueue的鏈表中,等待被Looper獲取處理
- Looper獲取到Message之后票渠,調(diào)用Message對應(yīng)的Handler處理Message
這樣整理的流程就清晰了逐哈,細(xì)節(jié)的源碼分析我就不再贅述了,如果有讀者哪個部分不夠清晰问顷,可以回到上面對應(yīng)部分再看一遍昂秃。
相關(guān)問題
主線程為什么不用初始化Looper?
答:因?yàn)閼?yīng)用在啟動的過程中就已經(jīng)初始化主線程Looper了杜窄。
每個java應(yīng)用程序都是有一個main方法入口肠骆,Android是基于Java的程序也不例外。Android程序的入口在ActivityThread的main方法中:
public static void main(String[] args) {
...
// 初始化主線程Looper
Looper.prepareMainLooper();
...
// 新建一個ActivityThread對象
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
// 獲取ActivityThread的Handler塞耕,也是他的內(nèi)部類H
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
// 如果loop方法結(jié)束則拋出異常蚀腿,程序結(jié)束
throw new RuntimeException("Main thread loop unexpectedly exited");
}
main方法中先初始化主線程Looper,新建ActivityThread對象,然后再啟動Looper莉钙,這樣主線程的Looper在程序啟動的時候就跑起來了廓脆。我們不需要再去初始化主線程Looper。
為什么主線程的Looper是一個死循環(huán)磁玉,但是卻不會ANR停忿?
答: 因?yàn)楫?dāng)Looper處理完所有消息的時候會進(jìn)入阻塞狀態(tài),當(dāng)有新的Message進(jìn)來的時候會打破阻塞繼續(xù)執(zhí)行蚊伞。
這其實(shí)沒理解好ANR這個概念席赂。ANR,全名Application Not Responding时迫。當(dāng)我發(fā)送一個繪制UI 的消息到主線程Handler之后颅停,經(jīng)過一定的時間沒有被執(zhí)行,則拋出ANR異常掠拳。Looper的死循環(huán)癞揉,是循環(huán)執(zhí)行各種事務(wù),包括UI繪制事務(wù)碳想。Looper死循環(huán)說明線程沒有死亡烧董,如果Looper停止循環(huán),線程則結(jié)束退出了胧奔。Looper的死循環(huán)本身就是保證UI繪制任務(wù)可以被執(zhí)行的原因之一。同時UI繪制任務(wù)有同步屏障预吆,可以更加快速地保證繪制更快執(zhí)行龙填。同步屏障下面會講。
Handler如何保證MessageQueue并發(fā)訪問安全拐叉?
答:循環(huán)加鎖岩遗,配合阻塞喚醒機(jī)制。
我們可以發(fā)現(xiàn)MessageQueue其實(shí)是“生產(chǎn)者-消費(fèi)者”模型凤瘦,Handler不斷地放入消息宿礁,Looper不斷地取出,這就涉及到死鎖問題蔬芥。如果Looper拿到鎖梆靖,但是隊(duì)列中沒有消息,就會一直等待笔诵,而Handler需要把消息放進(jìn)去返吻,鎖卻被Looper拿著無法入隊(duì),這就造成了死鎖乎婿。Handler機(jī)制的解決方法是循環(huán)加鎖测僵。在MessageQueue的next方法中:
Message next() {
...
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
...
}
}
}
我們可以看到他的等待是在鎖外的,當(dāng)隊(duì)列中沒有消息的時候谢翎,他會先釋放鎖捍靠,再進(jìn)行等待沐旨,直到被喚醒。這樣就不會造成死鎖問題了榨婆。
那在入隊(duì)的時候會不會因?yàn)殛?duì)列已經(jīng)滿了然后一邊在等待消息處理一邊拿著鎖呢希俩?這一點(diǎn)不同的是MessageQueue的消息沒有上限,或者說他的上限就是JVM給程序分配的內(nèi)存纲辽,如果超出內(nèi)存會拋出異常颜武,但一般情況下是不會的。
Looper退出后是否可以重新運(yùn)行拖吼?
答: 不可以鳞上。
線程的存活是靠Looper調(diào)用的next方法進(jìn)行阻塞實(shí)現(xiàn)的。如果Looper退出后吊档,那么線程會馬上結(jié)束篙议,也不會再有第二次運(yùn)行的機(jī)會了。即使線程還沒結(jié)束再一次調(diào)用loop()怠硼,Looper內(nèi)部有一個mQuitting變量鬼贱,當(dāng)他被賦值為false之后就無法再被賦值為true。所以就無法再重新運(yùn)行了香璃。
Handler是如何切換線程的这难?
答: 使用不同線程的Looper處理消息。
前面我們聊到葡秒,代碼的執(zhí)行線程姻乓,并不是代碼本身決定,而是執(zhí)行這段代碼的邏輯是在哪個線程眯牧,或者說是哪個線程的邏輯調(diào)用的蹋岩。每個Looper都運(yùn)行在對應(yīng)的線程,所以不同的Looper調(diào)用的dispatchMessage方法就運(yùn)行在其所在的線程了学少。
Handler的阻塞喚醒機(jī)制是怎么回事剪个?
答: Handler的阻塞喚醒機(jī)制是基于Linux的阻塞喚醒機(jī)制。
這個機(jī)制也是類似于handler機(jī)制的模式版确。在本地創(chuàng)建一個文件描述符扣囊,然后需要等待的一方則監(jiān)聽這個文件描述符,喚醒的一方只需要修改這個文件阀坏,那么等待的一方就會收到文件從而打破喚醒如暖。和Looper監(jiān)聽MessageQueue,Handler添加message是比較類似的忌堂。具體的Linux層知識讀者可通過這篇文章詳細(xì)了解(傳送門)
能不能讓一個Message加急被處理盒至?/ 什么是Handler同步屏障?
答:可以 / 一種使得異步消息可以被更快處理的機(jī)制
如果向主線程發(fā)送了一個UI更新的操作Message,而此時消息隊(duì)列中的消息非常多枷遂,那么這個Message的處理就會變得緩慢樱衷,造成界面卡頓。所以通過同步屏障酒唉,可以使得UI繪制的Message更快被執(zhí)行矩桂。
什么是同步屏障?這個“屏障”其實(shí)是一個Message痪伦,插入在MessageQueue的鏈表頭侄榴,且其target==null。Message入隊(duì)的時候不是判斷了target不能為null嗎网沾?不不不癞蚕,添加同步屏障是另一個方法:
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
// 把當(dāng)前需要執(zhí)行的Message全部執(zhí)行
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
// 插入同步屏障
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
可以看到同步屏障就是一個特殊的target,哪里特殊呢辉哥?target==null桦山,我們可以看到他并沒有給target屬性賦值。那這個target有什么用呢醋旦?看next方法:
Message next() {
...
// 阻塞時間
int nextPollTimeoutMillis = 0;
for (;;) {
...
// 阻塞對應(yīng)時間
nativePollOnce(ptr, nextPollTimeoutMillis);
// 對MessageQueue進(jìn)行加鎖恒水,保證線程安全
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
/**
* 1
*/
if (msg != null && msg.target == null) {
// 同步屏障,找到下一個異步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 下一個消息還沒開始饲齐,等待兩者的時間差
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 獲得消息且現(xiàn)在要執(zhí)行钉凌,標(biāo)記MessageQueue為非阻塞
mBlocked = false;
/**
* 2
*/
// 一般只有異步消息才會從中間拿走消息,同步消息都是從鏈表頭獲取
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 沒有消息箩张,進(jìn)入阻塞狀態(tài)
nextPollTimeoutMillis = -1;
}
// 當(dāng)調(diào)用Looper.quitSafely()時候執(zhí)行完所有的消息后就會退出
if (mQuitting) {
dispose();
return null;
}
...
}
...
}
}
這個方法我在前面講過甩骏,我們重點(diǎn)看一下關(guān)于同步屏障的部分,看注釋1的地方的代碼:
if (msg != null && msg.target == null) {
// 同步屏障先慷,找到下一個異步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
如果遇到同步屏障,那么會循環(huán)遍歷整個鏈表找到標(biāo)記為異步消息的Message咨察,即isAsynchronous返回true论熙,其他的消息會直接忽視,那么這樣異步消息摄狱,就會提前被執(zhí)行了脓诡。注釋2的代碼注意一下就可以了。
注意媒役,同步屏障不會自動移除祝谚,使用完成之后需要手動進(jìn)行移除,不然會造成同步消息無法被處理酣衷。從源碼中可以看到如果不移除同步屏障交惯,那么他會一直在那里,這樣同步消息就永遠(yuǎn)無法被執(zhí)行了。
有了同步屏障席爽,那么喚醒的判斷條件就必須再加一個:MessageQueue中有同步屏障且處于阻塞中意荤,此時插入在所有異步消息前插入新的異步消息。這個也很好理解只锻,跟同步消息是一樣的玖像。如果把所有的同步消息先忽視,就是插入新的鏈表頭且隊(duì)列處于阻塞狀態(tài)齐饮,這個時候就需要被喚醒了捐寥。看一下源碼:
boolean enqueueMessage(Message msg, long when) {
...
// 對MessageQueue進(jìn)行加鎖
synchronized (this) {
...
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
/**
* 1
*/
// 當(dāng)線程被阻塞祖驱,且目前有同步屏障握恳,且入隊(duì)的消息是異步消息
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
/**
* 2
*/
// 如果找到一個異步消息,說明前面有延遲的異步消息需要被處理羹膳,不需要被喚醒
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
// 如果需要則喚醒隊(duì)列
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
同樣睡互,這個方法我之前講過,把無關(guān)同步屏障的代碼忽視陵像,看到注釋1處的代碼就珠。如果插入的消息是異步消息,且有同步屏障醒颖,同時MessageQueue正處于阻塞狀態(tài)妻怎,那么就需要喚醒。而如果這個異步消息的插入位置不是所有異步消息之前泞歉,那么不需要喚醒逼侦,如注釋2。
那我們?nèi)绾伟l(fā)送一個異步類型的消息呢腰耙?有兩種辦法:
- 使用異步類型的Handler發(fā)送的全部Message都是異步的
- 給Message標(biāo)志異步
Handler有一系列帶Boolean類型的參數(shù)的構(gòu)造器榛丢,這個參數(shù)就是決定是否是異步Handler:
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
// 這里賦值
mAsynchronous = async;
}
在發(fā)送消息的時候就會給Message賦值:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// 賦值
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
但是異步類型的Handler構(gòu)造器是標(biāo)記為hide,我們無法使用挺庞,所以我們使用異步消息只有通過給Message設(shè)置異步標(biāo)志:
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
但是N蕖!Q∏取掖鱼!,其實(shí)同步屏障對于我們的日常使用的話其實(shí)是沒有多大用處援制。因?yàn)樵O(shè)置同步屏障和創(chuàng)建異步Handler的方法都是標(biāo)志為hide戏挡,說明谷歌不想要我們?nèi)ナ褂盟K赃@里同步屏障也作為一個了解晨仑,可以更加全面地理解源碼中的內(nèi)容褐墅。
什么是IdleHandler拆檬?
答: 當(dāng)MessageQueue為空或者目前沒有需要執(zhí)行的Message時會回調(diào)的接口對象。
IdleHandler看起來好像是個Handler掌栅,但他其實(shí)只是一個有單方法的接口秩仆,也稱為函數(shù)型接口:
public static interface IdleHandler {
boolean queueIdle();
}
在MessageQueue中有一個List存儲了IdleHandler對象,當(dāng)MessageQueue沒有需要被執(zhí)行的MessageQueue時就會遍歷回調(diào)所有的IdleHandler猾封。所以IdleHandler主要用于在消息隊(duì)列空閑的時候處理一些輕量級的工作澄耍。
IdleHandler的調(diào)用是在next方法中:
Message next() {
// 如果looper已經(jīng)退出了,這里就返回null
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// IdleHandler的數(shù)量
int pendingIdleHandlerCount = -1;
// 阻塞時間
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 阻塞對應(yīng)時間
nativePollOnce(ptr, nextPollTimeoutMillis);
// 對MessageQueue進(jìn)行加鎖晌缘,保證線程安全
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 同步屏障齐莲,找到下一個異步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 下一個消息還沒開始,等待兩者的時間差
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 獲得消息且現(xiàn)在要執(zhí)行磷箕,標(biāo)記MessageQueue為非阻塞
mBlocked = false;
// 一般只有異步消息才會從中間拿走消息选酗,同步消息都是從鏈表頭獲取
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 沒有消息,進(jìn)入阻塞狀態(tài)
nextPollTimeoutMillis = -1;
}
// 當(dāng)調(diào)用Looper.quitSafely()時候執(zhí)行完所有的消息后就會退出
if (mQuitting) {
dispose();
return null;
}
// 當(dāng)隊(duì)列中的消息用完了或者都在等待時間延遲執(zhí)行同時給pendingIdleHandlerCount<0
// 給pendingIdleHandlerCount賦值MessageQueue中IdleHandler的數(shù)量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 沒有需要執(zhí)行的IdleHanlder直接continue
if (pendingIdleHandlerCount <= 0) {
// 執(zhí)行IdleHandler岳枷,標(biāo)記MessageQueue進(jìn)入阻塞狀態(tài)
mBlocked = true;
continue;
}
// 把List轉(zhuǎn)化成數(shù)組類型
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 執(zhí)行IdleHandler
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // 釋放IdleHandler的引用
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
// 如果返回false芒填,則把IdleHanlder移除
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 最后設(shè)置pendingIdleHandlerCount為0,防止再執(zhí)行一次
pendingIdleHandlerCount = 0;
// 當(dāng)在執(zhí)行IdleHandler的時候空繁,可能有新的消息已經(jīng)進(jìn)來了
// 所以這個時候不能阻塞殿衰,要回去循環(huán)一次看一下
nextPollTimeoutMillis = 0;
}
}
代碼很多,可能看著有點(diǎn)亂盛泡,我梳理一下邏輯闷祥,然后再回去看源碼就會很清晰了:
- 當(dāng)調(diào)用next方法的時候,會給
pendingIdleHandlerCount
賦值為-1 - 如果隊(duì)列中沒有需要處理的消息的時候傲诵,就會判斷
pendingIdleHandlerCount
是否為<0
凯砍,如果是則把存儲IdleHandler的list的長度賦值給pendingIdleHandlerCount
- 把list中的所有IdleHandler放到數(shù)組中。這一步是為了不讓在執(zhí)行IdleHandler的時候List被插入新的IdleHandler拴竹,造成邏輯混亂
- 然后遍歷整個數(shù)組執(zhí)行所有的IdleHandler
- 最后給
pendingIdleHandlerCount
賦值為0悟衩。然后再回去看一下這個期間有沒有新的消息插入。因?yàn)?code>pendingIdleHandlerCount的值為0不是-1栓拜,所以IdleHandler只會在空閑的時候執(zhí)行一次 - 同時注意局待,如果IdleHandler返回了false,那么執(zhí)行一次之后就被丟棄了菱属。
建議讀者再回去把源碼看一遍,這樣邏輯會清晰很多舰罚。
Handler消息機(jī)制的再認(rèn)識
到這里關(guān)于Handler機(jī)制該講的已經(jīng)講得差不多了纽门。但不知讀者和我一樣是否有同樣的疑惑:
Handler機(jī)制為什么叫做Android中的消息機(jī)制?Handler真的就只是用來切換線程更新UI 的嗎营罢?怎么樣從源碼設(shè)計(jì)的角度來更好地理解Handler消息機(jī)制赏陵?
每次學(xué)習(xí)關(guān)于Android中的機(jī)制問題時饼齿,我都喜歡從研究他在android源碼設(shè)計(jì)中體現(xiàn)的作用,或者說思想蝙搔。這有助于讓我的理解提高一個層次缕溉。這里就簡單談?wù)勎覍andler機(jī)制的理解。
Handler機(jī)制吃型,之所以叫handler证鸥,我覺得只是因?yàn)槲覀兘佑|的都是Handler,所以叫做Handler機(jī)制勤晚,如果我們接觸Looper比較多可能他的名字就是Looper機(jī)制了枉层。更準(zhǔn)確來說,他應(yīng)該是Android消息機(jī)制赐写。
我們知道鸟蜡,每個java程序都有一個入口:main方法,然后我們從這里開始進(jìn)入我們的應(yīng)用程序挺邀。相信每個讀者都有使用c語言寫學(xué)生管理系統(tǒng)的經(jīng)歷揉忘,我們是如何讓程序暫停下來不要直接結(jié)束的?通過循環(huán)+輸入等待端铛。我們會在最外層寫一個死循環(huán)泣矛,然后不斷地監(jiān)聽輸入,再根據(jù)輸入執(zhí)行命令沦补。當(dāng)用戶無輸入的時候乳蓄,就會一直等待。這其實(shí)和Handler機(jī)制是類似的夕膀。Handler機(jī)制使用的是多線程的思路虚倒,主線程不斷等待消息,然后從別的線程發(fā)送消息讓主線程執(zhí)行邏輯产舞,這也稱為事務(wù)驅(qū)動型設(shè)計(jì)魂奥,主線程的邏輯都是通過message來驅(qū)動的。
我們直接來看一下Android應(yīng)用程序的main方法:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
AndroidOs.install();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
// 初始化Looper
Looper.prepareMainLooper();
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
// 創(chuàng)建ActivityThread
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// 啟動Looper
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
但是我們可以看到他的代碼其實(shí)并不多易猫,啟動了ActivityThread和Looper之后就沒有再執(zhí)行其他邏輯了耻煤,那我們的Activity是如何被調(diào)用并執(zhí)行邏輯的?通過Handler准颓。Android是事務(wù)驅(qū)動型的設(shè)計(jì)哈蝇,通過不斷地分發(fā)事務(wù)來讓整個程序運(yùn)行起來。熟悉Activity啟動流程的讀者應(yīng)該可以聯(lián)想到攘已,AMS通過binder機(jī)制和程序聯(lián)系炮赦,然后binder線程再發(fā)送一個消息給到主線程,主線程再執(zhí)行相對應(yīng)的邏輯样勃。他們的關(guān)系可以用下面的圖來表示:
當(dāng)應(yīng)用進(jìn)程被創(chuàng)建的時候吠勘,只是創(chuàng)建了主線程的Looper和handler性芬,以及其他的binder線程等。之后AMS通過Binder與應(yīng)用程序通信剧防,給主線程發(fā)送message植锉,讓程序執(zhí)行創(chuàng)建Activity等的操作。這樣的設(shè)計(jì)我們不用去寫死循環(huán)和等待用戶輸入等邏輯峭拘,應(yīng)用程序就能跑起來且不會結(jié)束俊庇。關(guān)于Activity的啟動相關(guān)我這里就不展開講了,讀者可以去看筆者的另一篇文章(Activity啟動流程詳解)棚唆。之后程序會開啟其他的線程來接收用戶的觸摸輸入等暇赤,然后把這些包裝成一個message發(fā)送到主線程去更新UI。
可以說宵凌,“無消息鞋囊,無安卓”,整個安卓的程序運(yùn)行都是基于這套消息機(jī)制來跑的瞎惫。他不僅僅只是切換線程這么簡單溜腐,他涉及到整個android程序的根基。
總結(jié)
這篇文章從一開始的入門講解瓜喇,到深入講解各個類的源碼和作用挺益,最后再升華一下整個消息機(jī)制的設(shè)計(jì)思想。相信讀者關(guān)于Handler消息機(jī)制的認(rèn)識已經(jīng)非常深刻了乘寒。
消息機(jī)制我們?nèi)粘J褂玫貌⒉欢嗤冢m然他非常重要,但我們的使用也是主要用戶切換線程更新UI這一塊伞辛。而我們有很多成熟且非常方便的框架可以使用:RxJava烂翰、kotlin協(xié)程等等。但由于Handler機(jī)制對于android程序?qū)嵲谑欠浅V匾槭希瑢τ谏钊雽W(xué)習(xí)android還是非常有必要去學(xué)習(xí)甘耿、去理解。
希望文章對你有幫助竿滨。
全文到此佳恬,原創(chuàng)不易,覺得有幫助可以點(diǎn)贊收藏評論轉(zhuǎn)發(fā)于游。
筆者能力有限毁葱,有任何想法歡迎評論區(qū)交流指正。
如需轉(zhuǎn)載請私信交流贰剥。另外歡迎光臨筆者的個人博客:傳送門