一、Handler原理
1. Looper和消息隊列機(jī)制
Handler持有了一個消息隊列MessageQueue
對象mQueue
趁啸。這個對象是Handler實例構(gòu)造的時候巨缘,通過Looper傳遞過來的尿赚。當(dāng)使用無參構(gòu)造方法時散庶,這個Looper為Looper.myLooper()
。
public Handler() {
// 凌净。悲龟。。
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
}
而Looper類又是通過 ThreadLocal 來實現(xiàn)線程和Looper對象一一對應(yīng)的冰寻。Looper.myLooper()
即是當(dāng)前線程所對應(yīng)的Looper须教。
也就是說,Handler中的消息隊列斩芭,其實是當(dāng)前線程對應(yīng)的Looper的消息隊列轻腺。
那么,要理解Handler的原理秒旋,就要先理解Looper和消息隊列的原理约计。
1.1 Looper
事情又要回到ActivityThread
中說起,這相當(dāng)于是一個Android程序的主線程迁筛,main
方法就在其中。
ActivityThread.main()
public static void main(String[] args) {
// ...
Looper.prepareMainLooper();
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
在這里耕挨,調(diào)用了Looper的兩個方法细卧,prepare
和loop
,啟動了主線程的Looper筒占。Looper的結(jié)構(gòu)相對來說還是比較簡單的贪庙,其中最主要的就是這兩個方法了。
Looper.prepare()
public static void prepare() {
prepare(true);
}
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));
}
在Looper內(nèi)部翰苫,有一個ThreadLocal<Looper>
類型的靜態(tài)變量sThreadLocal
止邮。
而prepare的過程很簡單,就是將當(dāng)前這個Looper對象奏窑,保存到這個sThreadLocal
中导披。即:以sThreadLocal
為媒介,建立當(dāng)前線程與當(dāng)前Looper對象的對應(yīng)關(guān)系埃唯。
Looper.loop()
這是Android程序中最重要的機(jī)制之一撩匕,也是Android程序能夠一直運行的原因。以下是Looper.loop()
源碼墨叛,刪去了絕大部分止毕,只保留了最關(guān)鍵的幾行:
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
try {
msg.target.dispatchMessage(msg);
} // ...
}
}
可以發(fā)現(xiàn)模蜡,loop()
函數(shù)其實是一個死循環(huán);在這個循環(huán)中扁凛,不停地從消息隊列中取下一條消息忍疾,然后分發(fā)給對應(yīng)的Handler(msg.target
)進(jìn)行處理。
“Looper”就是循環(huán)的意思谨朝,這正對應(yīng)了loop()
方法中的這個死循環(huán)膝昆。在這個死循環(huán)里面,可以無限讀取消息隊列中的消息叠必。使用quit()
方法可以退出這個循環(huán)荚孵;如果在主線程的Looper退出,也就意味著程序的結(jié)束——將會得到 java.lang.IllegalStateException: Main thread not allowed to quit.
的報錯信息纬朝。
1.2 消息隊列 - MessageQueue
-
Message
Message消息是Handler傳遞信息的基本單位收叶。在Handler中,不管是調(diào)用post(Runnable)
還是sendMessage(Message)
還是其他的什么共苛,最終都會構(gòu)建一個Message對象判没,并添加到消息隊列mQueue
中。Message中保存了消息的類型隅茎、參數(shù)澄峰、對應(yīng)的Handler等一些少量的數(shù)據(jù)。
在1.1中說到辟犀,Looper.loop()
會發(fā)起一個死循環(huán)俏竞,在消息隊列中不停取值。那么堂竟,為什么這里不會卡死呢魂毁?
Looper#loop()
:
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
try {
msg.target.dispatchMessage(msg);
} // ...
}
}
跳轉(zhuǎn)到MessageQueue.next()
,看一下消息隊列是怎么取下一條消息的出嘹。這里刪去了大部分代碼席楚,只剩下核心的部分,方便梳理流程:
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis); // 會阻塞線程直到下一個消息到來
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message msg = mMessages;
if (msg != null) {
if (now < msg.when) { // 時候未到
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mMessages = msg.next;
msg.next = null;
msg.markInUse();
return msg;
}
}
}
}
}
nativePollOnce
是一個本地方法税稼,它會阻塞線程直到下一個有消息來臨烦秩。這類似于Java中的DelayQueue
郎仆,不同點是DelayQueue
是通過優(yōu)先隊列+鎖實現(xiàn)的只祠。
在MessageQueue中有一個Message類型的對象mMessage
,它保存著消息隊列中最早的一條消息丸升。當(dāng)阻塞的線程被喚醒時铆农,next()
方法會返回mMessage
,并且mMessage
重新賦值為mMessage.next
。
很容易發(fā)現(xiàn)墩剖,在消息隊列中猴凹,所有未讀的消息是通過鏈表的形式來保存的,每一個Message都是鏈表中的節(jié)點岭皂,Message#next
指向了鏈表中的下一個節(jié)點郊霎。
小結(jié)
- Looper和擁有Looper的線程一一對應(yīng),通過ThreadLocal來實現(xiàn)爷绘。Looper和消息隊列也是一一對應(yīng)的书劝。
-
Looper.loop()
中是一個死循環(huán),會無限調(diào)用對應(yīng)消息隊列的next()
方法來獲取下一個消息土至。 - 消息隊列的
next()
方法會調(diào)用native方法nativePollOnce
阻塞線程购对,直到有新的消息來臨將其返回。 - 消息隊列實質(zhì)上是以
Message
為節(jié)點的單向鏈表陶因,其頭節(jié)點為mMessage
骡苞。鏈表是按照Message的觸發(fā)時間,即msg.when
楷扬,從早到晚排序的解幽。
2. Handler傳遞消息的過程
從發(fā)送消息開始。使用Handler的post(runnalbe)
烘苹、sendMessage(msg)
躲株、sendMessageDelayed(msg, delay)
、Message的sendToTarget()
等等許多方法都可以發(fā)送消息镣衡。最終霜定,這些方法都會進(jìn)入Handler#enqueueMessage
方法。
- Handler#enqueueMessage(MessageQueue, Message, long)
enqueueMessage
方法的作用是將Message的target設(shè)置為本Handler捆探,然后調(diào)用MessageQueue的enqueueMessage
方法然爆。
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);
}
這個queue
即是Looper.mQueue
,即當(dāng)前線程的消息隊列黍图。在這里,會使用msg.target = this
將Message與當(dāng)前Handler綁定奴烙。
這里的uptimeMillis
是Message預(yù)定送達(dá)的時間助被,如果沒有設(shè)置延遲,那么這個時間是SystemClock.uptimeMillis()
切诀。
- MessageQueue#enqueueMessage(Message, long)
顧名思義揩环,enqueueMessage
表示將新到來的消息入隊。
刪去了部分幅虑,只保留關(guān)鍵代碼:
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.when = when;
Message p = mMessages; // 鏈表頭
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
這里做了兩件事:
一丰滑、將這個Message按照傳達(dá)時間的順序插入到消息隊列中;
二倒庵、如果消息隊列當(dāng)前是阻塞的褒墨,并且這個消息的傳達(dá)時間成為了消息隊列中最早的一個炫刷,那么就將線程喚醒。
當(dāng)消息入隊之后郁妈,消息發(fā)送的過程就已經(jīng)完畢了浑玛,接下來就是等待取出消息了。
- MessageQueue#next()
- Looper#loop()
第1小節(jié)提到了噩咪,Looper會無限循環(huán)從MessageQueue中讀取消息顾彰。
public static void loop() {
for (;;) {
Message msg = queue.next(); // might block
try {
msg.target.dispatchMessage(msg);
} // ...
}
}
當(dāng)一個消息msg
到達(dá)指定時間并被讀取到之后,會調(diào)用msg.target.dispatchMessage()
方法胃碾;而這個target
對象就是上面Handler#enqueueMessage()
方法中傳遞進(jìn)去的Handler涨享。
也就是說,消息被讀取到之后仆百,會調(diào)用對應(yīng)Handler的dispatchMessage
方法厕隧。
- 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);
}
}
這里的邏輯很簡單,如果這個Handler設(shè)置了Callback
的話儒旬,就調(diào)用handleCallback
回調(diào)栏账,否則就調(diào)用handleMessage
回調(diào)≌辉矗可以看到挡爵,如果設(shè)置了Callback的話,Handler的handleMessage
就不生效了甚垦,這一點很容易證實茶鹃。
此時,就完成了從消息發(fā)送到消息處理的流程艰亮。
小結(jié)
Handler消息的傳遞流程:
->Handler#enqueueMessage(MessageQueue, Message, long)
所有發(fā)送消息最終調(diào)用該方法
->MessageQueue#enqueueMessage(Message, long)
消息入隊
->MessageQueue#next()
等待消息到時闭翩,取出消息
->Handler#dispatchMessage(msg)
發(fā)送消息到對應(yīng)Handler
->Handler#handleMessage(msg)
或Handler.Callback#handleCallback(msg)
:處理消息Handler相當(dāng)于一個前臺的工具人,只做了發(fā)送消息和接收消息的工作迄埃,消息處理的主要傳遞和分發(fā)過程都交給了Looper和MessageQueue疗韵。
二、Handler相關(guān)問題
1. 為什么Looper中的死循環(huán)不會阻塞主線程侄非?
回到標(biāo)題中的問題蕉汪。
從上面的分析中,可以看到逞怨,在Looper.loop()
的死循環(huán)中者疤,主線程的確是被阻塞了;并且如果沒有消息叠赦,將會一直阻塞下去——這一點毫無疑問驹马。所以這個問題本身就是不嚴(yán)謹(jǐn)?shù)模旱孟葐柺遣皇牵賳枮槭裁础?/p>
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
try {
msg.target.dispatchMessage(msg);
} // ...
}
}
這個問題正確的問法是,為什么主線程Looper中的死循環(huán)不會造成ANR/卡頓糯累?
雖然這個問題仍然沒有什么邏輯性——死循環(huán)和ANR/卡頓似乎并沒有什么太大的聯(lián)系算利,并且可以說是完全沒有關(guān)系。要回答這個問題寇蚊,首先要分析ANR/卡頓的原因笔时,這是一個綜合性的問題。
首先要明確的是仗岸,卡頓和ANR不是一回事兒允耿。
卡頓指手機(jī)不能在肉眼無法察覺的時間內(nèi)完成一幀的繪制。對于一個60hz的屏幕扒怖,每一幀需要在16ms內(nèi)完成繪制较锡,否則就會丟幀。丟幀越多盗痒,卡頓就越嚴(yán)重蚂蕴。卡頓的主要原因是主線程干了太多的事情,導(dǎo)致了16ms之內(nèi)無法完成一幀的繪制俯邓,可能是布局層次太深導(dǎo)致繪圖耗費時間長骡楼,可能是進(jìn)行了復(fù)雜的運算,還有可能是頻繁GC引發(fā)卡頓……總之稽鞭,就是主線程負(fù)擔(dān)太重了鸟整。
ANR指的是Application Not Responding,也就是應(yīng)用無響應(yīng)朦蕴。這里ANR特指輸入無響應(yīng)ANR篮条,也就是應(yīng)用對觸摸屏幕或者按鍵的響應(yīng)時間超過5秒。也就是說吩抓,ANR的發(fā)生的必要條件是涉茧,需要有輸入,也就是用戶點擊了屏幕之類的疹娶,否則是不會發(fā)生ANR的伴栓。當(dāng)用戶點擊了屏幕,InputManagerService通過epoll機(jī)制在硬件層面讀取到這個事件后雨饺,會使用InputDispatcher使用InputChannel通過Socket通知對應(yīng)的ViewRootImpl挣饥。而ANR是在InputDispatcher.cpp中的handleTargetsNotReadyLocked
函數(shù)檢測的,在這里沛膳,當(dāng)一個事件分發(fā)下去之后,會設(shè)置一個超時時間汛聚,也就5秒之后锹安;在下一個事件到來時,如果時間大于超時時間,并且上一個事件還沒有處理完畢的話叹哭,就要走ANR流程了忍宋。(見《ANR是如何產(chǎn)生的?》)
當(dāng)然风罩,究其根本原因糠排,造成ANR的原因很多情況下與卡頓是類似的,但是二者是完全不同性質(zhì)的兩個事件超升。
回到問題上入宦,卡頓是因為出于某種原因?qū)е碌睦L制時間過長,而ANR的原因是對用戶的操作響應(yīng)超時室琢。
而Looper中的死循環(huán)是為了讀取消息乾闰,要知道Android應(yīng)用本質(zhì)上是消息驅(qū)動的,不管是卡頓還是ANR盈滴,本質(zhì)上都是對應(yīng)Handler或者Handler.Callback的handleMessage()
處理消息方法的執(zhí)行時間太長涯肩;而Looper中的死循環(huán)是在體系之外的,不在某個Handler的handleMessage()
方法體之中巢钓,自然也就不會引起卡頓和ANR了病苗。
2. Handler只能在主線程創(chuàng)建嗎?如果不是症汹,那Handler可以在任意線程創(chuàng)建嗎硫朦?
否。
Handler的作用是作為一個終端發(fā)送和處理消息烈菌,需要配套的消息隊列才能發(fā)揮作用阵幸。所以,Handler只能在調(diào)用了Looper.prepare()
的線程中使用芽世,并且在最后加上Looper.loop()
使其生效挚赊。如果在沒有調(diào)用Looper.prepare()
的線程創(chuàng)建Handler,會出現(xiàn) "Can't create handler inside thread that has not called Looper.prepare()" 的錯誤济瓢。
同時荠割,這個線程的所有代碼都要寫在Looper.prepare()
和Looper.loop()
之間,因為Looper.loop()
會阻塞線程旺矾,后面的代碼沒法執(zhí)行到蔑鹦。
3. View.post()方法和Handler.post()是一樣的嗎?
View#post()
方法本質(zhì)上也是調(diào)用了Handler#post()
箕宙,這個Handler保存在View的mAttachInfo
中嚎朽,通過父容器調(diào)用View的dispatchAttachedToWindow(attachInfo, visibility)
方法傳遞過來。
這個Handler最終是指向ViewRootImpl中的mHandler
對象柬帕,類型是繼承自Handler類的ViewRootHandler
哟忍。這個類中定義了一系列View需要用到的消息并進(jìn)行了處理狡门,如INVALIDATE
等。
4. 獲取Message的方式有哪些锅很?哪種最好其馏?
- 直接new
- 調(diào)用
Message.obtain()
或者Handler#obtain()
第二種方法好,因為使用了消息池復(fù)用爆安。因為Message類本身就可以作為一個鏈表的節(jié)點叛复,所以消息池的數(shù)據(jù)結(jié)構(gòu)是一個鏈表,每次復(fù)用取出頭節(jié)點扔仓。
5. Handler是怎樣起到切換線程作用的褐奥?是怎樣在子線程發(fā)送消息然后在主線程處理的?
Handler發(fā)送消息的過程僅僅只是把消息放進(jìn)消息隊列里当辐,這是在子線程里完成的抖僵。
主線程的Looper是一直在主線程運行的,當(dāng)發(fā)現(xiàn)有新消息之后缘揪,就會提取出來耍群,然后再在主線程把消息傳遞給Handler進(jìn)行處理;這樣就完成了線程切換找筝。
6. 消息隊列的數(shù)據(jù)結(jié)構(gòu)是什么蹈垢?
鏈表。
7. Handler為什么會造成內(nèi)存泄漏袖裕?如何解決曹抬?
內(nèi)存泄漏的原因是類成員的生命周期大于類對象的生命周期,換句話說就是一個需要銷毀的對象由于成員被外部引用而無法銷毀急鳄。
比如谤民,一個Activity中有一個Handler成員對象。如果這個Handler發(fā)送了一個延時很長的消息疾宏,那么這個Handler在很長一段時間內(nèi)都不能銷毀张足,因為發(fā)送的消息的Message引用了這個Handler(msg.target
),而這個Message還在消息隊列中存活坎藐。這樣为牍,即使finish了這個Activity,它仍然會在內(nèi)存中存活岩馍,造成內(nèi)存泄漏碉咆。
解決方式一是使用static修飾Handler。如果Handler需要引用Activity蛀恩,那么使用WeakReference弱引用疫铜。二是在Activity銷毀的時候,在onDestroy()
回調(diào)中清除Handler的所有回調(diào)双谆。但是注意如果發(fā)送的消息周期的確是長于本Activity的块攒,那么就不能使用方法二了励稳。