前言
- 在 Android 中炸客,Handler 是貫穿于整個(gè)應(yīng)用的消息機(jī)制,在面試中出現(xiàn)的概率為:100%
- 在這篇文章里戈钢,我將帶你梳理 Handler 的使用攻略 & 設(shè)計(jì)原理痹仙。追求簡單易懂又不失深度,如果能幫上忙殉了,請務(wù)必點(diǎn)贊加關(guān)注开仰!
延伸文章
- 關(guān)于ThreadLocal,請閱讀:《Java | ThreadLocal 用法解析》
- 關(guān)于EventBus薪铜,請閱讀: 《Android | 這是一份詳細(xì)的 EventBus 使用教程》
目錄
1. 概述
在 Android 中众弓,很多地方是通過消息機(jī)制驅(qū)動的,例如線程間通信隔箍、四大組件的啟動等等谓娃。消息機(jī)制中主要涉及 的類有:Handler & Looper & MessageQueue & Message,其中 Handler 可以說是消息機(jī)制提供給 Java 層的上層接口蜒滩。
1.1 概念模型
問:Handler 怎么進(jìn)行線程通信滨达,原理是什么?
消息機(jī)制其實(shí)并不是 Android 系統(tǒng)獨(dú)有的設(shè)計(jì)俯艰,在很多系統(tǒng)設(shè)計(jì)中都可以看到消息機(jī)制的身影捡遍,例如 IOS 的 runLoop、Web 的 Ajax 和 Spring 的消息隊(duì)列等竹握。在所有系統(tǒng)設(shè)計(jì)的消息機(jī)制里画株,都會有生產(chǎn)者與消費(fèi)者的概念,如以下模型:
其中消息緩沖區(qū)的具體實(shí)現(xiàn)可以是棧 & 隊(duì)列啦辐,因?yàn)殛?duì)列(特別是優(yōu)先級隊(duì)列)是最常見的谓传,所以很多情況都會直接將消息緩沖區(qū)稱為消息隊(duì)列。
1.2 架構(gòu)圖
【類圖】
- Looper 里組合了 MessageQueue 消息隊(duì)列芹关,創(chuàng)建 Looper 的同時(shí)也創(chuàng)建了 MessageQueue
- MessageQueue 里組合了待處理的 Message 鏈表
- Message 持有用于處理消息的 Handler(target)
- Handler 被創(chuàng)建時(shí)需要聚合 Looper 與 MessageQueue续挟,默認(rèn)使用的是當(dāng)前線程的 Looper
1.3 消息的享元模式
消息機(jī)制里需要頻繁創(chuàng)建消息對象(Message),因此消息對象需要使用享元模式來緩存充边,以避免重復(fù)分配 & 回收內(nèi)存庸推。具體來說,Message 使用的是有容量限制的浇冰、無頭節(jié)點(diǎn)的單鏈表的對象池:
2. Handler 核心源碼分析
這一節(jié)我們來分析 Handler 的核心源碼:
2.1 啟動消息循環(huán)
- 問:Looper 如何在子線程中創(chuàng)建贬媒?(字節(jié)、小米)
要在哪個(gè)線程啟動消息循環(huán)肘习,就需要在該線程執(zhí)行Looper.prepare() & Looper.loop()
际乘。只有調(diào)用Looper.loop()
之后,消息循環(huán)才算真正運(yùn)轉(zhuǎn)起來了漂佩。具體來說脖含,啟動消息循環(huán)的分為兩種情況:主線程消息循環(huán) & 子線程消息循環(huán)罪塔,前者由 Framework 啟動,而后者需要我們自己啟動:
- 主線程消息循環(huán)
可以看到养葵,在應(yīng)用啟動時(shí)征堪,F(xiàn)ramework 已經(jīng)為主線程開啟了消息循環(huán),后續(xù)我們熟悉的startActivity & startService
都是通過主線程消息循環(huán)來驅(qū)動的关拒。
- 子線程消息循環(huán)
在子線程開啟消息循環(huán)佃蚜,我們需要自己調(diào)用Looper.prepare() & Looper.loop();
∽虐恚可以直接創(chuàng)建線程谐算,或者使用 HandlerThread,后者主要考慮的多線程中獲取 Looper 的同步問題归露,見 第 5.1 節(jié)洲脂。
小結(jié)一下:創(chuàng)建 Handler 的代碼需要放在Looper.prepare(); & Looper.loop();
中間執(zhí)行,這是因?yàn)閯?chuàng)建 Handler 對象時(shí)需要聚合 Looper 對象(默認(rèn)使用的是當(dāng)前線程的 Looper)剧包,而只有執(zhí)行Looper.prepare();
之后恐锦,才會創(chuàng)建該線程私有的 Looper 對象,否則創(chuàng)建 Handler 會拋異常玄捕。
2.2 Looper 線程唯一性
問:說一下 Looper踩蔚、handler、線程間的關(guān)系枚粘。例如一個(gè)線程可以對應(yīng)幾個(gè) Looper、幾個(gè)Handler飘蚯?
問:ThreadLocal 的原理馍迄,以及在 Looper 是如何應(yīng)用的?
每個(gè)線程只允許調(diào)用一次Looper.prepare()
局骤,否則會拋異常攀圈。這樣設(shè)計(jì)是因?yàn)橐粋€(gè) Looper 對應(yīng)了一個(gè)消息循環(huán),而一個(gè)線程進(jìn)行多個(gè)消息循環(huán)是沒有意義的(一個(gè)線程不可能同時(shí)進(jìn)行兩個(gè)死循環(huán))峦甩。那么赘来,Handler 是如何保證 Looper 線程唯一的呢?
答:首先凯傲,Handler 主要利用了 ThreadLocal 在每個(gè)線程單獨(dú)存儲副本的特性犬辰,保證了一個(gè)ThreadLocal<Looper>
在不同線程存取的Looper
對象相互獨(dú)立;其次冰单,ThreadLocal 是 Looper 的一個(gè)static final
變量幌缝,這樣就保證了整個(gè)進(jìn)程中 sThreadLocal
對象不可變;第三诫欠,Looper.prepare()
判斷在一個(gè)線程里重復(fù)調(diào)用涵卵,則會拋出異常浴栽。
關(guān)于 ThreadLocal 的原理分析,在這篇文章里轿偎,我們詳細(xì)討論:《Java | ThreadLocal 用法解析》典鸡,請關(guān)注!
2.3 消息發(fā)送
問:Handler#post(Runnable) 是如何執(zhí)行的坏晦?(字節(jié)椿每、小米)
問:Handler#sendMessage() 和 Handler#postDelay() 的區(qū)別?(字節(jié))
問:多個(gè) Handler 發(fā)消息時(shí)英遭,消息隊(duì)列如何保證線程安全间护?
問:為什么 MessageQueue 不設(shè)置消息上限?
消息發(fā)送的 API 非常多挖诸,最終它們都會調(diào)用到Handler#sendMessageAtTime(Message msg, long uptimeMillis)
汁尺,內(nèi)部會交給MessageQueue#enqueueMessage(Message msg, long when)
處理,梳理如下:
小結(jié)一下:
- 每個(gè)消息的處理時(shí)間
(when)
不一樣(SystemClock.uptimeMillis() + delayMill) - 消息入隊(duì)時(shí)多律,根據(jù)消息的處理時(shí)間
(when)
做插入排序痴突,隊(duì)頭的消息就是最需要執(zhí)行的消息 - 當(dāng)消息隊(duì)列為空時(shí)(無消息時(shí)線程會阻塞),消息入隊(duì)需要喚醒線程
- 當(dāng)消息隊(duì)列不為空時(shí)(一般不需要喚醒)狼荞,只有當(dāng)開啟同步屏障后第一個(gè)異步消息需要喚醒(開啟同步屏障時(shí)會在隊(duì)首插入一個(gè)占位消息辽装,此時(shí)消息隊(duì)列不為空,但是線程可能是阻塞的)相味,關(guān)于同步屏障的內(nèi)容見第 3 節(jié)
2.4 消息獲取
問:消息隊(duì)列無消息會怎么樣拾积?為什么 block 不會 ANR?
問:Looper 死循環(huán)為什么不會 ANR?(B站)
問:Looper 死循環(huán)為什么不阻塞主線程丰涉?
問:Handler內(nèi)存泄漏的原因拓巧?
上一節(jié)我們說到,消息入隊(duì)后 Looper 所在線程就會被喚醒(如果被阻塞)一死,以繼續(xù)消息循環(huán)肛度。在消息循環(huán)中,Looper.loop()
會死循環(huán)從 MessageQueue 獲取隊(duì)首的消息投慈,因?yàn)橄⒁呀?jīng)按照處理時(shí)間(when)
排序承耿,所以每次獲取的都是when
最小的消息:
【圖】 loop next
至于 Looper 死循環(huán)為什么不會 ANR?
消息隊(duì)列中無消息怎么處理 block
nativePollOnce值為-1表示無限等待,讓出cpu時(shí)間片給其線程伪煤,本線程等待
0表示無須等待直接返回
nativePollOnce -> epoll(linux) ->linux層的messagequeue
msg -> 5s -> ANRmsg
ANR:
5秒內(nèi)沒有響應(yīng)輸入事件加袋,比如按鍵、屏幕觸摸
10秒內(nèi)沒有處理廣播
本質(zhì):消息隊(duì)列中其他消息耗時(shí)带族,按鍵或廣播消息沒有及時(shí)處理
根本原因不是線程在睡眠锁荔,而是消息隊(duì)列被其他耗時(shí)消息阻塞,導(dǎo)致按鍵或廣播消息沒有及時(shí)處理
Handler內(nèi)存泄漏的原因
MessageQueue持有Message,Message持有activity
delay多久阳堕,message就會持有activity多久
方法:靜態(tài)內(nèi)部類跋理、弱引用
取到一個(gè)消息時(shí),如果when還不到恬总,則有限等待(nextPollTimeoutMills)nativePoll()
如果消息隊(duì)列沒有消息前普,則無限等待nativePoll(-1,),而消息入隊(duì)時(shí)壹堰,會執(zhí)行nativeWake()
quit也會nativeWake拭卿,喚醒Looper所在線程 => messagequeue返回null => Looper退出
2.5 消息分發(fā)
問:Message.callback 與 Handler.callback 哪個(gè)優(yōu)先?
問:Handler.callback 和 handlemessage() 都存在贱纠,但 callback 返回 true峻厚,handleMessage() 還會執(zhí)行么?(字節(jié)谆焊、小米)
獲取需要執(zhí)行的消息之后惠桃,將調(diào)用msg.target.dispatchMessage(msg);
處理消息,具體如下:
【圖】
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 1. 設(shè)置了Message.Callback(Runnable)
handleCallback(msg);
} else {
if (mCallback != null) {
// 2. 設(shè)置了 Handler.Callback(Callback )
if (mCallback.handleMessage(msg)) {
return;
}
}
// 3. 未設(shè)置 Handler.Callback 或 返回 false
handleMessage(msg);
}
}
public interface Callback {
public boolean handleMessage(Message msg);
}
可以看到辖试,除了在Handler#handleMessage(...)
中處理消息外辜王,Handler 機(jī)制還提供了兩個(gè) Callback 來增加消息處理的靈活性。具體來說罐孝,若設(shè)置了Message.Callback
則優(yōu)先執(zhí)行呐馆,否則判斷Handler.Callback
的返回結(jié)果,如果返回false
莲兢,則最后分發(fā)到Handler.handleMessage(...)
2.6 終止消息循環(huán)
quit:
mQuitting = true
removeAllMessage()
nativeWake() 喚醒汹来,程序從nativePollOnce(-1)開始執(zhí)行
主線程Looper不允許退出 quit() 拋異常 mQuitAllowed = false
ActivityThead#main looper.loop() 之后拋異常
原因:是handler驅(qū)動的機(jī)制,所有的事件都需要Handler處理怒见,例如LAUNCH_ACTIVITY等
3. Handler 同步屏障機(jī)制
同步屏障(SyncBarrier)是 Handler 用來篩選高低優(yōu)先級消息的機(jī)制俗慈,即:當(dāng)開啟同步屏障時(shí),高優(yōu)先級的異步消息優(yōu)先處理遣耍。
3.1 開啟同步屏障
3.2 關(guān)閉同步屏障
3.3 同步屏障下的消息循環(huán)
4. IdleHandler 機(jī)制
- 問:IdleHandler 是什么?怎么使用炮车,能解決什么問題舵变?
5. Handler 應(yīng)用場景
4.1 HandlerThread
Handler 都是在 Looper 所在線程創(chuàng)建的,但是有時(shí)候我們需要在其他線程中創(chuàng)建 Looper 所在線程的 Handler瘦穆,就需要考慮同步問題纪隙,使用 HandlerThread 可以簡化這種同步處理:
既然涉及多個(gè)線程的通信,會有同步的問題扛或,Android為了簡化Handler的創(chuàng)建過程绵咱,提供了HandlerThread類
wait - notifyAll - 避免prepare之前調(diào)用getLooper()
【重點(diǎn) 鎖的機(jī)制】
4.2 IntentService
處理完 service 自動停止 內(nèi)存釋放
4.3 Fragment 生命周期管理
attach -> commit
Glide生命周期管理 RequestManagerFragment 雙重檢查(避免連續(xù)兩次with()重復(fù)創(chuàng)建Fragment,因?yàn)閏ommit會發(fā)到Handle消息隊(duì)列的)
Handler是貫穿于Android的消息管理機(jī)制
所有的代碼都是在Handler上運(yùn)行的(loop()死循環(huán))
參考資料
- 《Android開發(fā)藝術(shù)探索》(第10章) —— 任玉剛 著
- 《Android進(jìn)階解密》(第3章) —— 劉望舒 著
- 《Android消息機(jī)制-Handler》 —— Gityuan 著
- 《揭秘 Android 消息機(jī)制之同步屏障:target==null 熙兔?》 —— hust_twj 著
推薦閱讀
- 密碼學(xué) | Base64是加密算法嗎悲伶?
- Java | 帶你理解 ServiceLoader 的原理與設(shè)計(jì)思想
- Java | 這是一篇全面的注解使用攻略(含 Kotlin)
- Java | 反射:在運(yùn)行時(shí)訪問類型信息(含 Kotlin)
- Android | 一個(gè)進(jìn)程有多少個(gè) Context 對象(答對的不多)
- Android | 帶你理解 NativeAllocationRegistry 的原理與設(shè)計(jì)思想
- 計(jì)算機(jī)組成原理 | Unicode 和 UTF-8是什么關(guān)系艾恼?
- 計(jì)算機(jī)組成原理 | 為什么浮點(diǎn)數(shù)運(yùn)算不精確?(阿里筆試)