Handler 原理
一鲸鹦、Handler消息發(fā)送機(jī)制
1. 發(fā)送消息
1.1 添加消息
調(diào)用Handler.sendMessageXX
方法發(fā)送消息蔗坯,這些方法最終都會(huì)調(diào)到MessageQueue
的enqueueMessage
方法中,MessageQueue
使用一個(gè)優(yōu)先隊(duì)列來保存添加的Message
對(duì)象零抬,執(zhí)行時(shí)間越早的Message
放的越靠近隊(duì)頭自晰。
//加入新的頭結(jié)點(diǎn)
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
//隊(duì)頭指針
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é)點(diǎn)
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
這樣就把一個(gè)Message
加入到了MessageQueue
中扁远。下面要看的就是怎么樣取出消息。
1.2 取出消息
在Looper.loop()
方法中通過一個(gè)死循環(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());//默認(rèn)不是異步的
}
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) {
//不是隊(duì)頭
prevMsg.next = msg.next;
} else {
//隊(duì)頭
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
方法中也有一個(gè)for
循環(huán)來從優(yōu)先隊(duì)列中找出一個(gè)符合條件的Message
刻像。
-
由于
MessageQueue
中保存的Message
是按有限順序排列的所以第一個(gè)一定是最先執(zhí)行的畅买,先取出第一個(gè),然后判斷這個(gè)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); }
如果不可以谷羞,計(jì)算還差多少時(shí)間可以處理這個(gè)
Message
,并把計(jì)算的時(shí)間賦值給nextPollTimeoutMillis
,這樣當(dāng)循環(huán)到下一次的時(shí)候就會(huì)調(diào)用nativePollOnce
這個(gè)方法來使本線程休眠,當(dāng)休眠時(shí)間到了之后就重新取出一個(gè)Message
然后返回湃缎,這樣犀填,MessageQueue.next
方法就執(zhí)行結(jié)束,loop
方法就能拿到一個(gè)新的Message
. -
Loop
方法拿到Message
之后判斷是否為空嗓违,如果是那loop
方法死循環(huán)就結(jié)束九巡,相當(dāng)于Looper
就停止工作。如果不為空就調(diào)用
Message.target.dispatchMessage
方法蹂季。在loop
方法拿Message
的過程中可能會(huì)因?yàn)?code>MessageQueue的休眠而導(dǎo)致卡主 -
Message.target
是一個(gè)Hander
的引用冕广,msg.target = this;
在Handler
的enqueueMessage
中會(huì)將當(dāng)前Handler
設(shè)置為Message.target
的引用蟀苛。所以這個(gè)方法最終也就是調(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); } }
這個(gè)方法最終會(huì)調(diào)到
handleMessage
方法中,也就是在創(chuàng)建Handler
實(shí)例時(shí)重寫的那個(gè)方法巢钓。這樣就能處理到這個(gè)Message
涕滋。
[圖片上傳失敗...(image-1acb02-1593160818812)]
二睬辐、Looper與線程的數(shù)量關(guān)系
結(jié)論:每條線程只有一個(gè)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
中維護(hù)了一個(gè)ThreadLoacal
的靜態(tài)變量,當(dāng)調(diào)用這個(gè)Looper
的prepare
方法時(shí)會(huì)先判斷當(dāng)前的線程是否有一個(gè)Looper
的實(shí)例宾肺,如果沒有就創(chuàng)建一個(gè)溉委。
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
維護(hù)了一個(gè)類似于HashMap
的數(shù)據(jù)結(jié)構(gòu)用線程號(hào)作為Key
,這樣就保證了每條線程只會(huì)有一個(gè)對(duì)應(yīng)的實(shí)例爱榕。
所以,Looper
是通過ThreadLocal
來保證每條線程只有一個(gè)Looper
實(shí)例坡慌。如果在同一個(gè)線程中多次調(diào)用prepare
方法就會(huì)拋出異常黔酥。
三、Handler內(nèi)存泄露
在實(shí)例化Handler
時(shí)使用匿名內(nèi)部類來重寫handleMessage
方法洪橘,這個(gè)Handler
實(shí)例就持有了外部類的引用(通常會(huì)是一個(gè)Activity
)跪者,在sendMessage
時(shí)又會(huì)使Message
引用當(dāng)前Handler
實(shí)例,這樣Message
就會(huì)最終引用到Activity
實(shí)例。由于Handler
發(fā)送的消息存在延時(shí)機(jī)制導(dǎo)致線程可能休眠熄求,這樣休眠線程中的Message
就會(huì)一直持有Activity
的引用渣玲。并且在prepare
時(shí)ThreadLocal
會(huì)持有創(chuàng)建的Looper
實(shí)例的引用,而靜態(tài)的ThreadLocal
又會(huì)被Looper.class
持有引用弟晚。Looper.class
是一個(gè)GCRoot
所以在可達(dá)性分析時(shí)就不會(huì)釋放Activity
忘衍。
[圖片上傳失敗...(image-170ef4-1593160818812)]
四、Handler的使用
1. 主線程上Handler的使用
在Looper
源碼中可以看到需用調(diào)用loop
方法才能才能從MessageQueue
中的取出消息并處理卿城,但是在Activity
中使用Handler
時(shí)枚钓,我們并不需要去手動(dòng)的prepare
來調(diào)用loop
方法。因?yàn)樵?code>Activity在啟動(dòng)的時(shí)候已經(jīng)由Android
自動(dòng)prepare
了MainLooper
瑟押。
在ActivityThread.main()
中已經(jīng)調(diào)用了Looper.prepareMainLooper
方法搀捷,這個(gè)main
方法時(shí)整個(gè)應(yīng)用啟動(dòng)的入口方法,所以在這個(gè)時(shí)候就會(huì)init
主線程中的Looper
。
2. 在子線程使用Looper
-
prepare()
先調(diào)用prepare
方法來在本線程創(chuàng)建一個(gè)Looper
實(shí)例多望, -
loop()
,調(diào)用loop
方法來從MessageQueue
中取出數(shù)據(jù) -
quit()/quitSafty()
清空MessageQueue退出``Looper
五嫩舟、多個(gè)線程往MessageQueue
中發(fā)消息如何保證線程安全
使用synchronized
關(guān)鍵字對(duì)enqueueMessage
和next
加上當(dāng)前MessageQueue
的鎖氢烘,這樣在多個(gè)線程中操作這個(gè)MessageQueue
時(shí)就能保證每次只有一個(gè)線程能執(zhí)行。所以發(fā)送Message的delay時(shí)間由于等待鎖的原因不一定是精確的家厌。
六播玖、如何創(chuàng)建一個(gè)Message
通過Message.obtain()
方法來獲取一個(gè)對(duì)象。Message
中維護(hù)了一個(gè)Message
對(duì)象的實(shí)例鏈表像街,當(dāng)調(diào)用obtain
方法時(shí)就從鏈表中取出一個(gè)Message
實(shí)例黎棠,并將這個(gè)Message
的標(biāo)志位重置。然后返回這個(gè)Message
實(shí)例镰绎。當(dāng)使用完一個(gè)Message
之后會(huì)調(diào)用recycleUnCheck()
方法來重置這個(gè)Message
,并將其加入到鏈表中脓斩。這里使用到了享元設(shè)計(jì)模式。
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個(gè)
synchronized (sPoolSync) {
if (sPoolSize < 50) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
七 畴栖、quit和quitSafty的區(qū)別
1. quit
這個(gè)方法直接移除MessageQueue中所有的Message
并將其重置随静。
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
2. quitSafty
這個(gè)不會(huì)移除當(dāng)前已經(jīng)到達(dá)執(zhí)行時(shí)間的Message
,會(huì)讓讓其正常處理完成吗讶,只會(huì)移除沒有到達(dá)處理時(shí)間的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é)點(diǎn)及之后的節(jié)點(diǎn)都還不到執(zhí)行的時(shí)間
break;
}
p = n;
}
p.next = null;
//將還不到執(zhí)行時(shí)間的message全部移除
do {
p = n;
n = p.next;
//把target置為null
p.recycleUnchecked();
} while (n != null);
}
}
}
在執(zhí)行著兩個(gè)方法之后會(huì)調(diào)用nativeWake(mPtr)
來喚醒在休眠的next
方法照皆,這個(gè)方法如果返回一個(gè)空的message
重绷,Looper
就會(huì)推出循環(huán)。
[圖片上傳失敗...(image-3daee6-1593160818812)]