一、Handler原理
1. Looper和消息隊列機制
Handler持有了一個消息隊列MessageQueue對象mQueue。這個對象是Handler實例構(gòu)造的時候,通過Looper傳遞過來的。當使用無參構(gòu)造方法時,這個Looper為Looper.myLooper()。
public Handler() {
// 。。。
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
}
而Looper類又是通過 ThreadLocal 來實現(xiàn)線程和Looper對象一一對應的。Looper.myLooper()即是當前線程所對應的Looper。
也就是說,Handler中的消息隊列,其實是當前線程對應的Looper的消息隊列。
那么,要理解Handler的原理,就要先理解Looper和消息隊列的原理。
1.1 Looper
事情又要回到ActivityThread中說起,這相當于是一個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的過程很簡單,就是將當前這個Looper對象,保存到這個sThreadLocal中。即:以sThreadLocal為媒介,建立當前線程與當前Looper對象的對應關系。
Looper.loop()
這是Android程序中最重要的機制之一,也是Android程序能夠一直運行的原因。以下是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);
} // ...
}
}
可以發(fā)現(xiàn),loop()函數(shù)其實是一個死循環(huán);在這個循環(huán)中,不停地從消息隊列中取下一條消息,然后分發(fā)給對應的Handler(msg.target)進行處理。
“Looper”就是循環(huán)的意思,這正對應了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ù)、對應的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,它保存著消息隊列中最早的一條消息。當阻塞的線程被喚醒時,next()方法會返回mMessage,并且mMessage重新賦值為mMessage.next。
很容易發(fā)現(xiàn),在消息隊列中,所有未讀的消息是通過鏈表的形式來保存的,每一個Message都是鏈表中的節(jié)點,Message#next指向了鏈表中的下一個節(jié)點。
小結(jié)
- Looper和擁有Looper的線程一一對應,通過ThreadLocal來實現(xiàn)。Looper和消息隊列也是一一對應的。
-
Looper.loop()中是一個死循環(huán),會無限調(diào)用對應消息隊列的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ā)送消息。最終,這些方法都會進入Handler#enqueueMessage方法。
- Handler#enqueueMessage(MessageQueue, Message, long)
enqueueMessage方法的作用是將Message的target設置為本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,即當前線程的消息隊列。在這里,會使用msg.target = this將Message與當前Handler綁定。
這里的uptimeMillis是Message預定送達的時間,如果沒有設置延遲,那么這個時間是SystemClock.uptimeMillis()。
- MessageQueue#enqueueMessage(Message, long)
顧名思義,enqueueMessage表示將新到來的消息入隊。
刪去了部分,只保留關鍵代碼:
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按照傳達時間的順序插入到消息隊列中;
二、如果消息隊列當前是阻塞的,并且這個消息的傳達時間成為了消息隊列中最早的一個,那么就將線程喚醒。
當消息入隊之后,消息發(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);
} // ...
}
}
當一個消息msg到達指定時間并被讀取到之后,會調(diào)用msg.target.dispatchMessage()方法;而這個target對象就是上面Handler#enqueueMessage()方法中傳遞進去的Handler。
也就是說,消息被讀取到之后,會調(diào)用對應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設置了Callback的話,就調(diào)用handleCallback回調(diào),否則就調(diào)用handleMessage回調(diào)??梢钥吹?,如果設置了Callback的話,Handler的handleMessage就不生效了,這一點很容易證實。
此時,就完成了從消息發(fā)送到消息處理的流程。
小結(jié)
Handler消息的傳遞流程:
->Handler#enqueueMessage(MessageQueue, Message, long)所有發(fā)送消息最終調(diào)用該方法
->MessageQueue#enqueueMessage(Message, long)消息入隊
->MessageQueue#next()等待消息到時,取出消息
->Handler#dispatchMessage(msg)發(fā)送消息到對應Handler
->Handler#handleMessage(msg)或Handler.Callback#handleCallback(msg):處理消息Handler相當于一個前臺的工具人,只做了發(fā)送消息和接收消息的工作,消息處理的主要傳遞和分發(fā)過程都交給了Looper和MessageQueue。
二、Handler相關問題
1. 為什么Looper中的死循環(huán)不會阻塞主線程?
回到標題中的問題。
從上面的分析中,可以看到,在Looper.loop()的死循環(huá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)系,并且可以說是完全沒有關系。要回答這個問題,首先要分析ANR/卡頓的原因,這是一個綜合性的問題。
首先要明確的是,卡頓和ANR不是一回事兒。
卡頓指手機不能在肉眼無法察覺的時間內(nèi)完成一幀的繪制。對于一個60hz的屏幕,每一幀需要在16ms內(nèi)完成繪制,否則就會丟幀。丟幀越多,卡頓就越嚴重。卡頓的主要原因是主線程干了太多的事情,導致了16ms之內(nèi)無法完成一幀的繪制,可能是布局層次太深導致繪圖耗費時間長,可能是進行了復雜的運算,還有可能是頻繁GC引發(fā)卡頓……總之,就是主線程負擔太重了。
ANR指的是Application Not Responding,也就是應用無響應。這里ANR特指輸入無響應ANR,也就是應用對觸摸屏幕或者按鍵的響應時間超過5秒。也就是說,ANR的發(fā)生的必要條件是,需要有輸入,也就是用戶點擊了屏幕之類的,否則是不會發(fā)生ANR的。當用戶點擊了屏幕,InputManagerService通過epoll機制在硬件層面讀取到這個事件后,會使用InputDispatcher使用InputChannel通過Socket通知對應的ViewRootImpl。而ANR是在InputDispatcher.cpp中的handleTargetsNotReadyLocked函數(shù)檢測的,在這里,當一個事件分發(fā)下去之后,會設置一個超時時間,也就5秒之后;在下一個事件到來時,如果時間大于超時時間,并且上一個事件還沒有處理完畢的話,就要走ANR流程了。(見《ANR是如何產(chǎn)生的?》)
當然,究其根本原因,造成ANR的原因很多情況下與卡頓是類似的,但是二者是完全不同性質(zhì)的兩個事件。
回到問題上,卡頓是因為出于某種原因?qū)е碌睦L制時間過長,而ANR的原因是對用戶的操作響應超時。
而Looper中的死循環(huán)是為了讀取消息,要知道Android應用本質(zhì)上是消息驅(qū)動的,不管是卡頓還是ANR,本質(zhì)上都是對應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需要用到的消息并進行了處理,如INVALIDATE等。
4. 獲取Message的方式有哪些?哪種最好?
- 直接new
- 調(diào)用
Message.obtain()或者Handler#obtain()
第二種方法好,因為使用了消息池復用。因為Message類本身就可以作為一個鏈表的節(jié)點,所以消息池的數(shù)據(jù)結(jié)構(gòu)是一個鏈表,每次復用取出頭節(jié)點。
5. Handler是怎樣起到切換線程作用的?是怎樣在子線程發(fā)送消息然后在主線程處理的?
Handler發(fā)送消息的過程僅僅只是把消息放進消息隊列里,這是在子線程里完成的。
主線程的Looper是一直在主線程運行的,當發(fā)現(xiàn)有新消息之后,就會提取出來,然后再在主線程把消息傳遞給Handler進行處理;這樣就完成了線程切換。
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的,那么就不能使用方法二了。