一、Handler原理
1. Looper和消息隊列機制
Handler持有了一個消息隊列MessageQueue
對象mQueue
。這個對象是Handler實例構造的時候,通過Looper傳遞過來的。當使用無參構造方法時,這個Looper為Looper.myLooper()
。
public Handler() {
// 。。。
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
}
而Looper類又是通過 ThreadLocal 來實現線程和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");
}
在這里,調用了Looper的兩個方法,prepare
和loop
,啟動了主線程的Looper。Looper的結構相對來說還是比較簡單的,其中最主要的就是這兩個方法了。
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內部,有一個ThreadLocal<Looper>
類型的靜態變量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);
} // ...
}
}
可以發現,loop()
函數其實是一個死循環;在這個循環中,不停地從消息隊列中取下一條消息,然后分發給對應的Handler(msg.target
)進行處理。
“Looper”就是循環的意思,這正對應了loop()
方法中的這個死循環。在這個死循環里面,可以無限讀取消息隊列中的消息。使用quit()
方法可以退出這個循環;如果在主線程的Looper退出,也就意味著程序的結束——將會得到 java.lang.IllegalStateException: Main thread not allowed to quit.
的報錯信息。
1.2 消息隊列 - MessageQueue
-
Message
Message消息是Handler傳遞信息的基本單位。在Handler中,不管是調用post(Runnable)
還是sendMessage(Message)
還是其他的什么,最終都會構建一個Message對象,并添加到消息隊列mQueue
中。Message中保存了消息的類型、參數、對應的Handler等一些少量的數據。
在1.1中說到,Looper.loop()
會發起一個死循環,在消息隊列中不停取值。那么,為什么這里不會卡死呢?
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);
} // ...
}
}
跳轉到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
是通過優先隊列+鎖實現的。
在MessageQueue中有一個Message類型的對象mMessage
,它保存著消息隊列中最早的一條消息。當阻塞的線程被喚醒時,next()
方法會返回mMessage
,并且mMessage
重新賦值為mMessage.next
。
很容易發現,在消息隊列中,所有未讀的消息是通過鏈表的形式來保存的,每一個Message都是鏈表中的節點,Message#next
指向了鏈表中的下一個節點。
小結
- Looper和擁有Looper的線程一一對應,通過ThreadLocal來實現。Looper和消息隊列也是一一對應的。
-
Looper.loop()
中是一個死循環,會無限調用對應消息隊列的next()
方法來獲取下一個消息。 - 消息隊列的
next()
方法會調用native方法nativePollOnce
阻塞線程,直到有新的消息來臨將其返回。 - 消息隊列實質上是以
Message
為節點的單向鏈表,其頭節點為mMessage
。鏈表是按照Message的觸發時間,即msg.when
,從早到晚排序的。
2. Handler傳遞消息的過程
從發送消息開始。使用Handler的post(runnalbe)
、sendMessage(msg)
、sendMessageDelayed(msg, delay)
、Message的sendToTarget()
等等許多方法都可以發送消息。最終,這些方法都會進入Handler#enqueueMessage
方法。
- Handler#enqueueMessage(MessageQueue, Message, long)
enqueueMessage
方法的作用是將Message的target設置為本Handler,然后調用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按照傳達時間的順序插入到消息隊列中;
二、如果消息隊列當前是阻塞的,并且這個消息的傳達時間成為了消息隊列中最早的一個,那么就將線程喚醒。
當消息入隊之后,消息發送的過程就已經完畢了,接下來就是等待取出消息了。
- MessageQueue#next()
- Looper#loop()
第1小節提到了,Looper會無限循環從MessageQueue中讀取消息。
public static void loop() {
for (;;) {
Message msg = queue.next(); // might block
try {
msg.target.dispatchMessage(msg);
} // ...
}
}
當一個消息msg
到達指定時間并被讀取到之后,會調用msg.target.dispatchMessage()
方法;而這個target
對象就是上面Handler#enqueueMessage()
方法中傳遞進去的Handler。
也就是說,消息被讀取到之后,會調用對應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
的話,就調用handleCallback
回調,否則就調用handleMessage
回調??梢钥吹?,如果設置了Callback的話,Handler的handleMessage
就不生效了,這一點很容易證實。
此時,就完成了從消息發送到消息處理的流程。
小結
Handler消息的傳遞流程:
->Handler#enqueueMessage(MessageQueue, Message, long)
所有發送消息最終調用該方法
->MessageQueue#enqueueMessage(Message, long)
消息入隊
->MessageQueue#next()
等待消息到時,取出消息
->Handler#dispatchMessage(msg)
發送消息到對應Handler
->Handler#handleMessage(msg)
或Handler.Callback#handleCallback(msg)
:處理消息Handler相當于一個前臺的工具人,只做了發送消息和接收消息的工作,消息處理的主要傳遞和分發過程都交給了Looper和MessageQueue。
二、Handler相關問題
1. 為什么Looper中的死循環不會阻塞主線程?
回到標題中的問題。
從上面的分析中,可以看到,在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);
} // ...
}
}
這個問題正確的問法是,為什么主線程Looper中的死循環不會造成ANR/卡頓?
雖然這個問題仍然沒有什么邏輯性——死循環和ANR/卡頓似乎并沒有什么太大的聯系,并且可以說是完全沒有關系。要回答這個問題,首先要分析ANR/卡頓的原因,這是一個綜合性的問題。
首先要明確的是,卡頓和ANR不是一回事兒。
卡頓指手機不能在肉眼無法察覺的時間內完成一幀的繪制。對于一個60hz的屏幕,每一幀需要在16ms內完成繪制,否則就會丟幀。丟幀越多,卡頓就越嚴重。卡頓的主要原因是主線程干了太多的事情,導致了16ms之內無法完成一幀的繪制,可能是布局層次太深導致繪圖耗費時間長,可能是進行了復雜的運算,還有可能是頻繁GC引發卡頓……總之,就是主線程負擔太重了。
ANR指的是Application Not Responding,也就是應用無響應。這里ANR特指輸入無響應ANR,也就是應用對觸摸屏幕或者按鍵的響應時間超過5秒。也就是說,ANR的發生的必要條件是,需要有輸入,也就是用戶點擊了屏幕之類的,否則是不會發生ANR的。當用戶點擊了屏幕,InputManagerService通過epoll機制在硬件層面讀取到這個事件后,會使用InputDispatcher使用InputChannel通過Socket通知對應的ViewRootImpl。而ANR是在InputDispatcher.cpp中的handleTargetsNotReadyLocked
函數檢測的,在這里,當一個事件分發下去之后,會設置一個超時時間,也就5秒之后;在下一個事件到來時,如果時間大于超時時間,并且上一個事件還沒有處理完畢的話,就要走ANR流程了。(見《ANR是如何產生的?》)
當然,究其根本原因,造成ANR的原因很多情況下與卡頓是類似的,但是二者是完全不同性質的兩個事件。
回到問題上,卡頓是因為出于某種原因導致的繪制時間過長,而ANR的原因是對用戶的操作響應超時。
而Looper中的死循環是為了讀取消息,要知道Android應用本質上是消息驅動的,不管是卡頓還是ANR,本質上都是對應Handler或者Handler.Callback的handleMessage()
處理消息方法的執行時間太長;而Looper中的死循環是在體系之外的,不在某個Handler的handleMessage()
方法體之中,自然也就不會引起卡頓和ANR了。
2. Handler只能在主線程創建嗎?如果不是,那Handler可以在任意線程創建嗎?
否。
Handler的作用是作為一個終端發送和處理消息,需要配套的消息隊列才能發揮作用。所以,Handler只能在調用了Looper.prepare()
的線程中使用,并且在最后加上Looper.loop()
使其生效。如果在沒有調用Looper.prepare()
的線程創建Handler,會出現 "Can't create handler inside thread that has not called Looper.prepare()" 的錯誤。
同時,這個線程的所有代碼都要寫在Looper.prepare()
和Looper.loop()
之間,因為Looper.loop()
會阻塞線程,后面的代碼沒法執行到。
3. View.post()方法和Handler.post()是一樣的嗎?
View#post()
方法本質上也是調用了Handler#post()
,這個Handler保存在View的mAttachInfo
中,通過父容器調用View的dispatchAttachedToWindow(attachInfo, visibility)
方法傳遞過來。
這個Handler最終是指向ViewRootImpl中的mHandler
對象,類型是繼承自Handler類的ViewRootHandler
。這個類中定義了一系列View需要用到的消息并進行了處理,如INVALIDATE
等。
4. 獲取Message的方式有哪些?哪種最好?
- 直接new
- 調用
Message.obtain()
或者Handler#obtain()
第二種方法好,因為使用了消息池復用。因為Message類本身就可以作為一個鏈表的節點,所以消息池的數據結構是一個鏈表,每次復用取出頭節點。
5. Handler是怎樣起到切換線程作用的?是怎樣在子線程發送消息然后在主線程處理的?
Handler發送消息的過程僅僅只是把消息放進消息隊列里,這是在子線程里完成的。
主線程的Looper是一直在主線程運行的,當發現有新消息之后,就會提取出來,然后再在主線程把消息傳遞給Handler進行處理;這樣就完成了線程切換。
6. 消息隊列的數據結構是什么?
鏈表。
7. Handler為什么會造成內存泄漏?如何解決?
內存泄漏的原因是類成員的生命周期大于類對象的生命周期,換句話說就是一個需要銷毀的對象由于成員被外部引用而無法銷毀。
比如,一個Activity中有一個Handler成員對象。如果這個Handler發送了一個延時很長的消息,那么這個Handler在很長一段時間內都不能銷毀,因為發送的消息的Message引用了這個Handler(msg.target
),而這個Message還在消息隊列中存活。這樣,即使finish了這個Activity,它仍然會在內存中存活,造成內存泄漏。
解決方式一是使用static修飾Handler。如果Handler需要引用Activity,那么使用WeakReference弱引用。二是在Activity銷毀的時候,在onDestroy()
回調中清除Handler的所有回調。但是注意如果發送的消息周期的確是長于本Activity的,那么就不能使用方法二了。