1匹涮、handler的作用
handler是android線程之間的消息機制,主要的作用是將一個任務(wù)切換到指定的線程中去執(zhí)行,(準(zhǔn)確的說是切換到構(gòu)成handler的looper所在的線程中去出處理)android系統(tǒng)中的一個例子就是主線程中的所有操作都是通過主線程中的handler去處理的。
2伐弹、handler的架構(gòu)
Handler的運行需要底層的 messagequeue和 looper做支撐。
3榨为、handler原理
3 .1 惨好、首先說messagequeue,messagequeue 是 一 個 消 息 隊 列 随闺, 它是采用單鏈表的數(shù)據(jù)結(jié)構(gòu)來存儲消息的日川,因為單鏈表在插入刪除上 的效率非常高。(Meaasgequeue主要包含一個是插入消 息的 enqueuemessage方法矩乐,和一個取出一條消息的next方法逗鸣。)
3.2、然后說 looper绰精,looper在安卓的消息機制中是扮演著消息調(diào)度的角色撒璧,具體來說就是他會不停的從 messagequeue中查看 是否有新消息,如果有笨使,并且這個消息需要執(zhí)行卿樱,就從隊列中取出這個消息進行執(zhí)行,(死循環(huán)遍歷消息:取消息的線程會先阻塞一段時間(隊頭消息的執(zhí)行時間減去當(dāng)前時間)硫椰,然后從隊列中取出隊頭消息)繁调,否則會一直阻塞在messagequeue的next那里。(構(gòu)成 一個 looper是需要一個 messagequeue靶草,而構(gòu)成一個 handler則需 要一個 looper蹄胰,)另外looper一般是調(diào)用Looper.prepare()方法使用 threadlocal在線程的ThreadLocalMap中存儲一個looper的,線程中有了looper之后就可以在這個線程中創(chuàng)建一個 handler了奕翔。
3.2裕寨、然后說 looper,looper在安卓的消息機制中是扮演著消息調(diào)度的角色派继。
Looper取消息的過程是這樣的:
如果隊列中有消息:
1宾袜、判斷隊頭消息的執(zhí)行時間是否大于當(dāng)前時間,如果大于驾窟,就調(diào)用nativePollOnce阻塞一段時間(隊頭消息的執(zhí)行時間-當(dāng)前時間)然后取出隊頭消息進行執(zhí)行庆猫。
2、否則就立即取出隊頭消息進行執(zhí)行绅络。
3月培、如果隊列中沒有消息嘁字,就一直阻塞,直到下一個消息來到杉畜,才喚醒取消息的線程繼續(xù)上述循環(huán)拳锚。
Message next() {
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
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.
//發(fā)現(xiàn)屏障消息,優(yōu)先執(zhí)行隊列中的異步消息
do {
prevMsg = msg;
msg = msg.next;
//之所以要循環(huán)寻行,是因為隊列是按時間排序的,異步消息不一定位于隊頭
} 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.
//隊頭消息沒到執(zhí)行時間匾荆,休眠nextPollTimeoutMillis
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
//得到一個需要立即執(zhí)行的消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// No more messages.
//隊列中沒有消息了拌蜘,進入無限期休眠
nextPollTimeoutMillis = -1;
}
...
}
}
nativePollOnce(ptr, nextPollTimeoutMillis),這是一個native方法牙丽,實際作用就是通過Native層的MessageQueue休眠nextPollTimeoutMillis毫秒的時間简卧。
1.如果nextPollTimeoutMillis=-1,一直休眠不會超時烤芦。
2.如果nextPollTimeoutMillis=0举娩,不會休眠,立即返回构罗。
3.如果nextPollTimeoutMillis>0铜涉,最長休眠nextPollTimeoutMillis毫秒(超時),如果期間有程序喚醒會立即返回遂唧。
3.3芙代、最后說 handler
handler通過handler.send (message)發(fā)送消息,實際上是往構(gòu)成他的 looper的 messagequeue中 插入了一條消息盖彭,在將這條消息插入 messagequeue中之前纹烹,他需 要將此消息的 target變量指向當(dāng)前發(fā)它的 handler,然后looper在適當(dāng)?shù)臅r機取出這個消息召边,(looper發(fā)現(xiàn)構(gòu)成它的 messagequeue中有消息時铺呵, looper的 loop方法就會從 messagequeue中取出這條消息),然后調(diào) 用這個消息對應(yīng)的handler的dispatchmessage方法來處理這個消息(即msg.target.dispatchmessage)隧熙,注意片挂,dispatchmessage 方法是在構(gòu)成 handler的 looper中的loop方法中調(diào)用的,所以處理消息的邏輯就切換到了構(gòu)成handler的looper所在的線程之中了贞盯。
messagequeue加入一條消息喚醒取消息線程的三種情況
boolean enqueueMessage(Message msg, long when) {
...
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//隊列為空|當(dāng)前消息不是延遲消息|當(dāng)前消息執(zhí)行時間小于隊頭消息執(zhí)行時間
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 {
//p.target == null && msg.isAsynchronous()代表這個消息是系統(tǒng)消息
needWake = mBlocked && p.target == null && msg.isAsynchronous();
//根據(jù)這個消息的執(zhí)行時間去將這個消息插入到適當(dāng)?shù)奈恢? Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
//隊列中有系統(tǒng)消息宴卖,不需要喚醒
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
//需要的情況下喚醒取消息線程
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
1、messagequeue為空
2邻悬、當(dāng)前消息不是延時消息
3症昏、當(dāng)前消息執(zhí)行時間小于隊頭消息執(zhí)行時間
(hanlder的構(gòu)成是需要一個 looper,主線程 之中父丰,在activitythread的main方法中(程序入口)通過 looper.preparemainlooper在主線 程中存儲一個 looper肝谭,而在子線程之中掘宪,我們則需要手動的通過 looper的 prepare在子線程中存儲一個 looper,然后通過 looper.loop 開啟一個消息循環(huán))攘烛。
4魏滚、主線程中的handler
android系統(tǒng)中的一個例子就是主線程中的所有操作都是通過主線程中的handler去處理的。例如activity的生命周期方法調(diào)用就是通過主線程中的handler去處理的坟漱。
在app的主線程中有一個類是activitythread鼠次,這個類中有一個main方法是app程序的入口,在main方法中使用Looper.prepareMainLooper()芋齿,在主線程中設(shè)置了一個looper腥寇,然后創(chuàng)建了一個applicationthread的線程用于和server進程中applicationthreadproxy進行進程通信,最后調(diào)用了Looper.loop()開啟消息循環(huán)觅捆。
在activity的生命中期中赦役,比如說系統(tǒng)服務(wù)ActivityManagerService調(diào)用applicationthreadproxy通過Binder給當(dāng)前app進程中的applicationthread發(fā)送了一個暫停activity的操作。app進程中的applicationthread便會通過在主線程中的handler將這個暫停activity的消息插入到主線程的messagequeue中去處理栅炒。
5掂摔、主線程的死循環(huán)一直運行是不是特別消耗CPU資源呢?
這里就涉及到Linux pipe/epoll機制赢赊,在主線程的MessageQueue沒有消息時乙漓,主線程便阻塞在loop的queue.next()中的nativePollOnce()方法里,相當(dāng)于java層的線程waite機制释移,此時主線程會釋放CPU資源進入休眠狀態(tài)簇秒,直到下個消息到達時調(diào)用nativewake,通過往pipe管道寫端寫入數(shù)據(jù)來喚醒主線程工作秀鞭。相當(dāng)于java層的notify機制趋观,去喚醒主線程,然后處理消息锋边,所以主線程大多數(shù)時候都是處于休眠狀態(tài)皱坛,并不會消耗大量CPU資源。
從looper取消息的過程豆巨,可以看出取消息的線程大部分時候處于阻塞狀態(tài)剩辟,不會消耗cpu資源。
6往扔、 Android中為什么主線程不會因為Looper.loop()里的死循環(huán)導(dǎo)致(anr)卡死贩猎?
理解1:looper死循環(huán)是不斷的往構(gòu)成它的消息隊列中取消息,如果當(dāng)前隊列中沒有消息萍膛,或者隊列中的消息不需要現(xiàn)在立即處理吭服,looper所在的線程就進入wait狀態(tài),釋放cpu資源蝗罗,其他的線程仍可處理事件艇棕。比如說applicationthread仍可接收服務(wù)進程中的消息來處理蝌戒,如果這個消息需要在主線程中處理,它就會調(diào)用主線程中的handler沼琉,將這個消息加到主線程的消息隊列中去處理北苟。
理解2:looper取消息的過程是先wait一段時間(這段時間是messagequeue隊首消息的執(zhí)行時間減去當(dāng)前時間),然后醒來從messagequeue中取出隊首消息進行執(zhí)行打瘪,wait過程中主線程是釋放cpu資源的友鼻,其他的線程(applicationthread)仍可處理事件。
理解3:(首先主線程中的死循環(huán)不會導(dǎo)致app anr闺骚,它會使得主線程阻塞在messagequeue的next中的nativePollOnce()方法里彩扔,當(dāng)有消息來時就會喚醒主線程進行消息處理,即使主線程在休眠的時候也有其他的線程(applicationthread)在處理事件葛碧。)
首先Anr和死循環(huán)不是一個概念。
1过吻、主線程的工作就是處理主線程中的message进泼,所有需要到主線程執(zhí)行的操作都是通過主線程的向主線程的messagequeue加入一條消息,looper在適當(dāng)?shù)臅r機取出這條消息纤虽,來執(zhí)行的乳绕,如果不是一個死循環(huán),那么loop取出一條消息逼纸,執(zhí)行完一條消息后洋措,主線程就退出了,正是有這個死循環(huán)杰刽,它才保證了主線程不會退出菠发,并且能處理隊列中所有的消息。所以這個死循環(huán)是一個消息處理機制贺嫂。
2滓鸠、而anr原因是,主線程中messagequeue中一個message的處理時間過長第喳,導(dǎo)致接下來的某類消息無法處理糜俗,比如說一個消息的處理時間超過了5秒,導(dǎo)致用戶的輸入無法響應(yīng)曲饱,才會出現(xiàn)anr悠抹。
3、另外扩淀,從looper取消息的過程來看楔敌,只有當(dāng)此刻有需要執(zhí)行的消息時,主線程才將此消息取出來執(zhí)行驻谆,否則就進入休眠狀態(tài)梁丘,釋放cpu侵浸。(就算不進入休眠一直循環(huán),如果手機是多核氛谜,也是不會卡死的掏觉,只是主線程在不停的運行代碼,消耗了更多資源)
為什么主線程中會采用死循環(huán)呢值漫?
線程是一段可執(zhí)行的代碼澳腹,當(dāng)可執(zhí)行代碼執(zhí)行完成后,線程生命周期便該終止了杨何。而對于主線程酱塔,我們是絕不希望會被運行一段時間就退出,所以采用死循環(huán)保證它不會被退出危虱。
7羊娃、handler.postDelayed(Runnable r, long delayMillis)
handler.postDelayed在message加入到messagequeue中之前,會計算出這個消息的執(zhí)行時間SystemClock.uptimeMillis() + delayMillis埃跷,(SystemClock.uptimeMillis()是開機到現(xiàn)在的時間(毫秒))蕊玷,然后通過enqueueMessage 將message和其執(zhí)行的時間一起添加進messagequeue,在enqueueMessage方法中會根據(jù)這個消息的執(zhí)行時間去將這個消息插入到適當(dāng)?shù)奈恢妹直ⅲ唵蔚恼f垃帅,messagequeue是按消息的執(zhí)行時間message.when排序的。如果插在隊列中間剪勿,說明該消息不需要馬上處理贸诚,不需要由這個消息來喚醒隊列。 如果插在隊列頭部(或者when=0)厕吉,則表明可能要馬上處理這個消息酱固。如果當(dāng)前隊列正在堵塞,則需要喚醒它進行處理头朱。 通過nativeWake方法喚醒隊列媒怯。
8、如何保證延時消息精確執(zhí)行髓窜?
1扇苞、從looper取消息過程來看,
2寄纵、從加入一條新的消息過程來看鳖敷。
這兩個過程都不存在任何延遲
8、ThreadLocal
ThreadLocal 可以在多個線程內(nèi)存儲數(shù)據(jù)程拭,使用ThreadLocal存儲的數(shù)據(jù)在多個線程之間是隔離的定踱,因為它是將數(shù)據(jù)存儲在每個線程內(nèi)的ThreadLocalMap中。
9恃鞋、同步屏障崖媚,同步消息亦歉,異步消息
同步消息就是我們平時發(fā)送的消息,異步消息一般是系統(tǒng)發(fā)送的消息
同步屏障就是給消息隊列發(fā)送了一個屏障信息(target == null)畅哑,消息隊列在處理到這個屏障信息時就開啟了”同步屏障”模式肴楷。在該模式下,只返回異步消息給 Looper 處理荠呐,屏蔽同步消息赛蔫。
在處理完異步消息隊列后,即使消息隊列中還有同步消息也會通過nativePollOnce()進入線程阻塞狀態(tài)泥张。直到有新的異步消息進來呵恢。除非解除同步屏障模式,同步消息才能得到處理媚创。
簡單的說就是渗钉,發(fā)現(xiàn)了同步屏障消息,就只處理異步消息钞钙,不處理同步消息鳄橘,直到同步屏障被移除。
view的刷新用到了同步屏障歇竟,因為界面刷新事件應(yīng)處在第一優(yōu)先級挥唠。
參考文章
8.1抵恋、handler.postDelayed(Runnable r, long delayMillis):https://stevendxc.github.io/Blog/2015/01/18/postDelayed/
8.2焕议、handler流程:http://www.reibang.com/p/8862bd2b6a29
8.3、handler系列問題:https://www.zhihu.com/question/34652589
8.4弧关、https://segmentfault.com/a/1190000022551209
8.5https://peterxiaosa.github.io/2020/07/15/Handler%E6%9C%BA%E5%88%B6%E4%B9%8B%E6%B6%88%E6%81%AF%E7%9A%84%E5%90%8C%E6%AD%A5%E5%B1%8F%E9%9A%9C/