-
簡述
當(dāng)接觸一個新事物的時候我們要去了解它,認(rèn)知過程一般是三個為題:
- 這個事物是什么?
- 它為什么要這么做?
- 它是怎樣做到的?
-
是什么?
handler 就是Google設(shè)計的一個線程之間通訊的工具,實現(xiàn)線程之間的切換工作。
-
為什么要這么做?
這要從Google定制的規(guī)則來說起了,Google提出,UI更新一定要在UI線程中實現(xiàn),這樣做就是提高移動端的效率用使用體驗。android 的UI線程是采用非安全線程(單線程模型)來實現(xiàn)的,
這樣保證了UI更新的流暢,提升了用戶體驗,但是非安全線程就會出現(xiàn)多個線程同時操作UI的情況,這種情況下UI會容易出現(xiàn)不可控的錯誤,所以Google
讓所有更新UI的動作都在主線程(ActivityThread),這樣就不會出現(xiàn)一些不可控的錯誤,并且在UI線程中不能耗時,耗時的話就可能阻塞UI線程,
UI得不到及時更新,出現(xiàn)卡頓,嚴(yán)重的直接出現(xiàn)"ANR"錯誤,說到底這一切的設(shè)計都是為了用戶體驗。這樣就出現(xiàn)一個矛盾點:有時候確實需要做一些耗時的動作,完成動作后還需要更新UI,這些耗時動作只能在子線程中進(jìn)行,更新UI又需要到UI線程,
所以為了解決矛盾,Handler的設(shè)計就出現(xiàn)了。 -
怎么做到的?
打開handler源碼就能看到handler的介紹:
handler介紹.jpg
大致意思是:
Handler是用來結(jié)合線程的消息隊列來發(fā)送、處理“Message對象”和“Runnable對象”的工具。
每一個Handler實例之后會關(guān)聯(lián)一個線程和該線程的消息隊列。
當(dāng)你創(chuàng)建一個Handler的時候,從這時開始,它就會自動關(guān)聯(lián)到所在的線程/消息隊列,
然后它就會陸續(xù)把Message/Runnalbe分發(fā)到消息隊列,并在它們出隊的時候處理掉。Handler有兩個主要用途:
1. 推送未來某個時間點將要執(zhí)行的Message或者Runnable到消息隊列。 2. 在子線程把需要在另一個線程執(zhí)行的操作加入到消息隊列中去。為應(yīng)用程序創(chuàng)建進(jìn)程時,其主線程專用于運行消息隊列,
該隊列負(fù)責(zé)管理頂級應(yīng)用程序?qū)ο螅ɑ顒樱瑥V播接收器等)及其創(chuàng)建的任何窗口。
您可以創(chuàng)建自己的線程,并通過Handler與主應(yīng)用程序線程進(jìn)行通信。
這是通過調(diào)用與以前相同的post或sendMessage方法完成的,但是來自您的新線程。
然后,將在Handler的消息隊列中調(diào)度給定的Runnable或Message,并在適當(dāng)時進(jìn)行處理。- Handler工作原理
handler 主要職責(zé)就是發(fā)送消息與處理消息,一下是發(fā)送消息最終調(diào)用源碼。
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
//判斷MessageQueue是否為空
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//將 Handler 自身賦值給了 msg.target
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 調(diào)用MessageQueue 的 enqueueMessage方法,將 Message 添加到消息隊列
return queue.enqueueMessage(msg, uptimeMillis);
}
怎么做到的
public void dispatchMessage(Message msg) {
//檢查 Message 的 callback 是否為 null
if (msg.callback != null) {
//不為 null 直接通過 handleCallback 來處理消息
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//callback為空并且mCallback 為空的情況下調(diào)用handleMessage()方法,
//這個方法通常就是我們實現(xiàn)的處理方法
handleMessage(msg);
}
}
- MessageQueue 消息隊列工作原理
MessageQueue 就是一個裝Message的容器,這是最直接的理解,既然是容器,其職責(zé)肯定是添加和移除消息了。上面介紹handler工作原理的時候
提到handler發(fā)送消息的最后是調(diào)用MessageQueue的enqueueMessage方法來將消息添加到消息隊列里面的,以下是實際添加message方法源碼:
boolean enqueueMessage(Message msg, long when) {
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.this鎖
synchronized (this) {
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;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//如果隊列為空,或者當(dāng)前處理的時間點為0(when的數(shù)值,when表示Message將要執(zhí)行的時間點),
//或者當(dāng)前Message需要處理的時間點先于隊列中的首節(jié)點,那么就將Message放入隊列首部
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;
// 遍歷隊列中Message,找到when比當(dāng)前Message的when大的Message,
//將Message插入到該Message之前,如果沒找到則將Message插入到隊列最后
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.
//判斷是否需要喚醒,一般是當(dāng)前隊列為空的情況下,next那邊會進(jìn)入睡眠,需要enqueue這邊喚醒next函數(shù)
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
-
Looper的工作原理
Looper 的作用就是不斷的查詢MessageQueue中是否有消息,如果有消息就取出消息給handler處理,如果沒有消息就阻塞住。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper 的構(gòu)造函數(shù)中創(chuàng)建了一個可MessageQueue 并且將當(dāng)前的線程保存起來。Looper通過Looper.loop()開啟循環(huán)來查看是否有消息。
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
* 通過調(diào)用quit() 去結(jié)束loop
*/
public static void loop() {
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 (;;) {
Message msg = queue.next(); // 調(diào)用 MessageQueue 的 next 方法獲取 Message
if (msg == null) {
// No message indicates that the message queue is quitting.
// msg == null 表示 MessageQueue 正在退出(調(diào)用了quit等方法)
return;
}
//....
try {
// msg.target 就是發(fā)送消息的Handler,因此這里將消息交回 Handler
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//....
}
}
注意到loop()中有一個死循環(huán),死循環(huán)結(jié)束的條件是msg==null,怎樣讓msg==null呢?調(diào)用MessageQueue的quit方法使msg為空。
是否想過一個問題,主線程Looper中運行一個死循環(huán)為什么不會出現(xiàn)ANR?
首先要明白一點,Java的線程是允許休眠的。
Android在主線程中是開啟了一個阻塞式死循環(huán),保證程序一直運行,什么是阻塞式死循環(huán),簡單的理解就是:
有事務(wù)就去處理,沒有的話就休眠。阻塞式循環(huán)的實現(xiàn)是在Linux中實現(xiàn)的(epoll和inotify機(jī)制)。ANR的本質(zhì)原因是因為MessageQueue中的事件沒能夠得到及時的處理,
并不是死循環(huán)取事件造成,所以死循環(huán)并不是ANR的必然原因。
如果我們自己在主線程寫死循環(huán)會怎樣?答案是,必然ANR,原因是我們的死循環(huán)進(jìn)入了不會阻塞,不會釋放cup資源,后面的事件無法處理,Looper的死循環(huán)屬于可阻塞式死循環(huán)。
- 看完handler源碼我們應(yīng)該很簡單的回答下面問題了。
-
在UI線程中有幾個Looper對象?有幾個MessageQueue對象?有幾個Handler對象?有幾個Message對象?
一個線程中只有一個Looper對象,一個MessageQueue對象,但是可以有多個Handler對象和多個Message對象。
-
怎么保證只有一個Looper對象的?
在handler體系中Looper對象出現(xiàn)在的地方有三個,一是在ActivityThread類中使用Looper.prepareMainLooper()創(chuàng)建Looper對象,
二是在hanlder類中使用Looper對象,三是在Looper的loop方法中使用Looper對象處理消息。而這三個Looper都是從ThreadLocal中取到,
ThreadLocal是Looper類的靜態(tài)字段,所以只有一個ThreadLocal對象。 prepare方法在UI線程被調(diào)用,所以只有在Ui線程才能從ThreadLocal對象中獲取到looper對象。 怎么保證只有一個MessageQueue對象的?
首先要明白,MessageQueue對象產(chǎn)生的地方,在Looper的構(gòu)造方法中會產(chǎn)生MessageQueue對象,而上一題已經(jīng)說明一個線程中只有一個Looper,所以對應(yīng)的MessageQueue也只有一個。-
為什么發(fā)送消息在子線程,而處理消息就變成主線程了,在哪兒跳轉(zhuǎn)的?
看源碼我們知道,handler發(fā)消息最終是調(diào)用的MessageQueue中的 enqueueMessage 方法,這個方法就將子線程中的消息提交到了MessageQueue中了,而MessageQueue與Looper是依賴關(guān)系的,
處于同一線程,這個線程又是UI線程,所以消息取出的時候就跳轉(zhuǎn)到UI線程中了。 -
looper對象只有一個,在分發(fā)消息時怎么區(qū)分不同的handler?
Message類中有個字段target,這個字段是Handler類型的,Handler的enqueueMessage方法中有這么一句代碼:msg.target = this;即把handler對象與Message綁定在了一起。
Looper類的looper方法中分發(fā)消息的代碼是:msg.target.dispatchMessage(msg)。在發(fā)送消息時handler對象與Message對象綁定在了一起。
在分發(fā)消息時首先取出Message對象,然后就可以得到與它綁定在一起的Handler對象了。 -
能不能在子線程中創(chuàng)建Handler對象?
子線程中是能創(chuàng)建handler的,就像之前提到的handler只是線程間的通訊工具,我們所說的UI線程也只是一個普通的線程,只是UI線程中已經(jīng)存在Looper對象,
在子線程中創(chuàng)建Handler 需要依賴MessageQueue,因為handler發(fā)送消息是依賴messageQueue中的enqueueMessage,而MessageQueue又是依賴于Looper的,
所以在子線程中創(chuàng)建Handler只要保證子線程中存在Looper,從handler構(gòu)造函數(shù)也可以看出如果Looper為空的話是會直接報異常的。 -
怎么在子線程中得到主線程中handler對象?
handler其實與線程是無關(guān)的,handler依賴的Looper與線程有關(guān),當(dāng)Looper是主線程中的Looper,handler就會是主線程中的,handler有一個構(gòu)造函數(shù)是需要傳入一個Looper對象,
這個對象就能確定handler所屬線程。 Handler導(dǎo)致內(nèi)存泄漏的根本原因是什么?
由上面的講解知道主線程中的Looper,其生命周期應(yīng)該是伴隨整個應(yīng)用的生命周期的,可以理解為與Application 生命周期一致,在使用handler的時候,handler 發(fā)送消息,其Message是
是對handler有引用關(guān)系的 "Handler的enqueueMessage方法中有這么一句代碼:msg.target = this,即把handler對象與Message綁定在了一起",而Message的存放取出與Looper有這緊密關(guān)系
引用關(guān)系鏈:handler ---->> Message ------>> MessageQueue ----->> Looper ,主線程中Looper 的生命周期是整個應(yīng)用的生命周期,如果我們的handler對activity 有引用,
那么當(dāng)activity結(jié)束生命周期的時候(activity應(yīng)該被回收),由于Looper 還在執(zhí)行message的相關(guān)操作,導(dǎo)致handler無法釋放對activity的引用,最后activity一直無法釋放出現(xiàn)內(nèi)存泄漏(引用鏈的存在,GC不會去回收).
