Handler 原理
一、Handler消息發(fā)送機制
1. 發(fā)送消息
1.1 添加消息
調(diào)用Handler.sendMessageXX方法發(fā)送消息,這些方法最終都會調(diào)到MessageQueue的enqueueMessage方法中,MessageQueue使用一個優(yōu)先隊列來保存添加的Message對象,執(zhí)行時間越早的Message放的越靠近隊頭。
//加入新的頭結(jié)點
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;
//遍歷鏈表,把最快執(zhí)行的msg放到表頭
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//插入新的Message節(jié)點
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
這樣就把一個Message加入到了MessageQueue中。下面要看的就是怎么樣取出消息。
1.2 取出消息
在Looper.loop()方法中通過一個死循環(huán)來不斷的從MessageQueue中取出Message
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//使本線程休眠
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());//默認不是異步的
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
//不是隊頭
prevMsg.next = msg.next;
} else {
//隊頭
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
在MessageQueue方法中也有一個for循環(huán)來從優(yōu)先隊列中找出一個符合條件的Message。
-
由于
MessageQueue中保存的Message是按有限順序排列的所以第一個一定是最先執(zhí)行的,先取出第一個,然后判斷這個Message是否可以開始處理,if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); }如果不可以,計算還差多少時間可以處理這個
Message,并把計算的時間賦值給nextPollTimeoutMillis,這樣當循環(huán)到下一次的時候就會調(diào)用nativePollOnce這個方法來使本線程休眠,當休眠時間到了之后就重新取出一個Message然后返回,這樣,MessageQueue.next方法就執(zhí)行結(jié)束,loop方法就能拿到一個新的Message. -
Loop方法拿到Message之后判斷是否為空,如果是那loop方法死循環(huán)就結(jié)束,相當于Looper就停止工作。如果不為空就調(diào)用
Message.target.dispatchMessage方法。在loop方法拿Message的過程中可能會因為MessageQueue的休眠而導致卡主 -
Message.target是一個Hander的引用,msg.target = this;在Handler的enqueueMessage中會將當前Handler設置為Message.target的引用。所以這個方法最終也就是調(diào)用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); } }這個方法最終會調(diào)到
handleMessage方法中,也就是在創(chuàng)建Handler實例時重寫的那個方法。這樣就能處理到這個Message。
[圖片上傳失敗...(image-1acb02-1593160818812)]
二、Looper與線程的數(shù)量關(guān)系
結(jié)論:每條線程只有一個Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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中維護了一個ThreadLoacal的靜態(tài)變量,當調(diào)用這個Looper的prepare方法時會先判斷當前的線程是否有一個Looper的實例,如果沒有就創(chuàng)建一個。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocal維護了一個類似于HashMap的數(shù)據(jù)結(jié)構(gòu)用線程號作為Key,這樣就保證了每條線程只會有一個對應的實例。
所以,Looper是通過ThreadLocal來保證每條線程只有一個Looper實例。如果在同一個線程中多次調(diào)用prepare方法就會拋出異常。
三、Handler內(nèi)存泄露
在實例化Handler時使用匿名內(nèi)部類來重寫handleMessage方法,這個Handler實例就持有了外部類的引用(通常會是一個Activity),在sendMessage時又會使Message引用當前Handler實例,這樣Message就會最終引用到Activity實例。由于Handler發(fā)送的消息存在延時機制導致線程可能休眠,這樣休眠線程中的Message就會一直持有Activity的引用。并且在prepare時ThreadLocal會持有創(chuàng)建的Looper實例的引用,而靜態(tài)的ThreadLocal又會被Looper.class持有引用。Looper.class是一個GCRoot所以在可達性分析時就不會釋放Activity。
[圖片上傳失敗...(image-170ef4-1593160818812)]
四、Handler的使用
1. 主線程上Handler的使用
在Looper源碼中可以看到需用調(diào)用loop方法才能才能從MessageQueue中的取出消息并處理,但是在Activity中使用Handler時,我們并不需要去手動的prepare來調(diào)用loop方法。因為在Activity在啟動的時候已經(jīng)由Android自動prepare了MainLooper。
在ActivityThread.main()中已經(jīng)調(diào)用了Looper.prepareMainLooper方法,這個main方法時整個應用啟動的入口方法,所以在這個時候就會init主線程中的Looper。
2. 在子線程使用Looper
-
prepare()先調(diào)用prepare方法來在本線程創(chuàng)建一個Looper實例, -
loop(),調(diào)用loop方法來從MessageQueue中取出數(shù)據(jù) -
quit()/quitSafty()清空MessageQueue退出``Looper
五、多個線程往MessageQueue中發(fā)消息如何保證線程安全
使用synchronized關(guān)鍵字對enqueueMessage和next加上當前MessageQueue的鎖,這樣在多個線程中操作這個MessageQueue時就能保證每次只有一個線程能執(zhí)行。所以發(fā)送Message的delay時間由于等待鎖的原因不一定是精確的。
六、如何創(chuàng)建一個Message
通過Message.obtain()方法來獲取一個對象。Message中維護了一個Message對象的實例鏈表,當調(diào)用obtain方法時就從鏈表中取出一個Message實例,并將這個Message的標志位重置。然后返回這個Message實例。當使用完一個Message之后會調(diào)用recycleUnCheck()方法來重置這個Message,并將其加入到鏈表中。這里使用到了享元設計模式。
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
//最大保存50個
synchronized (sPoolSync) {
if (sPoolSize < 50) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
七 、quit和quitSafty的區(qū)別
1. quit
這個方法直接移除MessageQueue中所有的Message并將其重置。
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
2. quitSafty
這個不會移除當前已經(jīng)到達執(zhí)行時間的Message,會讓讓其正常處理完成,只會移除沒有到達處理時間的Message。
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
if (p.when > now) {
//所有的消息都還不執(zhí)行,直接將其移除
removeAllMessagesLocked();
} else {
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
//n節(jié)點及之后的節(jié)點都還不到執(zhí)行的時間
break;
}
p = n;
}
p.next = null;
//將還不到執(zhí)行時間的message全部移除
do {
p = n;
n = p.next;
//把target置為null
p.recycleUnchecked();
} while (n != null);
}
}
}
在執(zhí)行著兩個方法之后會調(diào)用nativeWake(mPtr)來喚醒在休眠的next方法,這個方法如果返回一個空的message,Looper就會推出循環(huán)。
[圖片上傳失敗...(image-3daee6-1593160818812)]