我們會(huì)經(jīng)常使用 Handler
去 send
或 post
一個(gè)延時(shí)铆隘、非延時(shí)或插隊(duì)執(zhí)行的 Message
鸠真,但對(duì)于這個(gè) Message 到底什么時(shí)候執(zhí)行以及為什么是這樣馍刮,鮮少細(xì)究過徊件。本文將一 一盤點(diǎn)梭依,并起底個(gè)中原理!
同時(shí)針對(duì)大家疏于了解的異步執(zhí)行 Message 和空閑執(zhí)行 IdleHandler
惕虑,進(jìn)行演示和原理普及坟冲。篇幅較大,搭配收藏食用更佳~
目錄一覽:
- 非延時(shí)執(zhí)行 Message
- 延時(shí)執(zhí)行 Message
- 插隊(duì)執(zhí)行 Message
- 異步執(zhí)行 Message
- 空閑執(zhí)行 “Message”
- 名詞回顧
非延時(shí)執(zhí)行 Message
先在主線程創(chuàng)建一個(gè) Handler 并復(fù)寫 Callback
處理溃蔫。
private val mainHandler = Handler(Looper.getMainLooper()) { msg ->
Log.d(
"MainActivity",
"Main thread message occurred & what:${msg.what}"
)
true
}
不斷地發(fā)送期望即刻執(zhí)行的 Message 和 Runnable 給主線程的 Handler健提。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
testSendNoDelayedMessages()
}
private fun testSendNoDelayedMessages() {
Log.d("MainActivity","testSendNoDelayedMessages() start")
testSendMessages()
testPostRunnable()
Log.d("MainActivity","testSendNoDelayedMessages() end ")
}
private fun testSendMessages() {
Log.d("MainActivity","startSendMessage() start")
for (i in 1..10) {
sendMessageRightNow(mainHandler, i)
}
Log.d("MainActivity","startSendMessage() end ")
}
private fun testPostRunnable() {
Log.d("MainActivity","testPostRunnable() start")
for (i in 11..20) {
mainHandler.post { Log.d("MainActivity", "testPostRunnable() run & i:${i}") }
}
Log.d("MainActivity","testPostRunnable() end ")
}
什么時(shí)候執(zhí)行?
公布下日志前伟叛,大家可以猜測下運(yùn)行的結(jié)果私痹,Message 或 Runnable 在 send 或 post 之后會(huì)否立即執(zhí)行。不是的話统刮,什么時(shí)候執(zhí)行紊遵?
D MainActivity: testSendNoDelayedMessages() start
D MainActivity: startSendMessage() start
D MainActivity: startSendMessage() end
D MainActivity: testPostRunnable() start
D MainActivity: testPostRunnable() end
D MainActivity: testSendNoDelayedMessages() end
D MainActivity: Main thread message occurred & what:1
...
D MainActivity: Main thread message occurred & what:10
D MainActivity: testPostRunnable() run & i:11
...
D MainActivity: testPostRunnable() run & i:20
答案可能跟預(yù)想的略有出入,但一細(xì)想好像又是合理的:發(fā)送完的 Message 或 Runnable 不會(huì)立即執(zhí)行侥蒙,MessageQueue
的喚醒和回調(diào)需要等主線程的其他工作完成之后才能執(zhí)行暗膜。
為什么?
非延時(shí)的 sendMessage()
和 post()
最終仍然是調(diào)用 sendMessageAtTime()
將 Message 放入了 MessageQueue
鞭衩。只不過它的期待執(zhí)行時(shí)間 when
變成了 SystemClock.uptimeMillis()
学搜,即調(diào)用的時(shí)刻。
// Handler.java
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); // when 等于當(dāng)前時(shí)刻
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
return enqueueMessage(queue, msg, uptimeMillis);
}
這些 Message 會(huì)按照 when 的先后排隊(duì)進(jìn)入 MessageQueue 中论衍,當(dāng) Message 滿足了條件會(huì)立即調(diào)用 wake
瑞佩,反之只是插入隊(duì)列而已。所以坯台,上述的 send 或 post 循環(huán)炬丸,會(huì)按照調(diào)用的先后挨個(gè)進(jìn)入隊(duì)列,第一個(gè) Message 會(huì)觸發(fā) wake蜒蕾。
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
...
// 鑒于多線程往 Handler 里發(fā)送 Message 的情況
// 在向隊(duì)列插入 Message 前需要上鎖
synchronized (this) {
...
msg.markInUse(); // Message 標(biāo)記正在使用
msg.when = when; // 更新 when 屬性
Message p = mMessages; // 拿到隊(duì)列的 Head
boolean needWake;
// 如果隊(duì)列為空
// 或者 Message 需要插隊(duì)(sendMessageAtFrontOfQueue)
// 又或者 Message 執(zhí)行時(shí)刻比 Head 的更早
// 該 Message 插入到隊(duì)首
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
// 線程是否因?yàn)闆]有可執(zhí)行的 Message 正在 block 或 wait
// 是的話稠炬,喚醒
needWake = mBlocked;
} else {
// 如果隊(duì)列已有 Message焕阿,Message 優(yōu)先級(jí)又不高,同時(shí)執(zhí)行時(shí)刻并不早于隊(duì)首的 Message
// 如果線程正在 block 或 wait酸纲,或建立了同步屏障(target 為空)捣鲸,并且 Message 是異步的,則喚醒
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 遍歷隊(duì)列闽坡,找到 Message 目標(biāo)插入位置
for (;;) {
prev = p;
p = p.next;
// 如果已經(jīng)遍歷到隊(duì)尾了,或 Message 的時(shí)刻比當(dāng)前 Message 要早
// 找到位置了愁溜,退出遍歷
if (p == null || when < p.when) {
break;
}
// 如果前面決定需要喚醒疾嗅,但隊(duì)列已有執(zhí)行時(shí)刻更早的異步 Message 的話,先不喚醒
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 將 Message 插入隊(duì)列的目標(biāo)位置
msg.next = p;
prev.next = msg;
}
// 需要喚醒的話冕象,喚醒 Native 側(cè)的 MessageQueue
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
總結(jié)來講:
- 第一次 send 的 Message 在 enqueue 進(jìn) MessageQueue 的隊(duì)首后代承,通知 Native 側(cè) wake
- 后續(xù)發(fā)送的其他 Message 或 Runnable 挨個(gè) enqueue 進(jìn)隊(duì)列
- 接著執(zhí)行主線程的其他 Message,比如日志的打印
- 空閑后 wake 完畢并在 next() 的下一次循環(huán)里將隊(duì)首 Message 移除和返回給 Looper 去回調(diào)和執(zhí)行
- 之后 loop() 開始讀取 MessageQueue 當(dāng)前隊(duì)首 Message 的下一次循環(huán)渐扮,當(dāng)前時(shí)刻必然晚于 send 時(shí)候設(shè)置的when论悴,所以隊(duì)列里的 Message 挨個(gè)出隊(duì)和回調(diào)
結(jié)論
非延時(shí) Message 并非立即執(zhí)行,只是放入 MessageQueue 等待調(diào)度而已墓律,執(zhí)行時(shí)刻不確定膀估。
MessageQueue 會(huì)記錄請(qǐng)求的時(shí)刻,按照時(shí)刻的先后順序進(jìn)行排隊(duì)耻讽。如果 MessageQueue 中積攢了很多 Message察纯,或主線程被占用的話,Message 的執(zhí)行會(huì)明顯晚于請(qǐng)求的時(shí)刻针肥。
比如在 onCreate() 里發(fā)送 message 的話饼记,你會(huì)發(fā)現(xiàn)當(dāng) onResume() 執(zhí)行完才會(huì)回調(diào)你的 Message。原因在于 onCreate() 等操作也是由 Message 觸發(fā)慰枕,其同步處理完 onResume() 之后具则,才有機(jī)會(huì)進(jìn)入下一次循環(huán)去讀取你的 Message。
延時(shí)執(zhí)行 Message
延時(shí)執(zhí)行的 Message 使用更為常見具帮,那它又是何時(shí)執(zhí)行呢博肋?
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
testSendDelayedMessages()
}
private fun testSendDelayedMessages() {
Log.d("MainActivity","testSendDelayedMessages() start")
// 發(fā)送 Delay 2500 ms 的 Message
sendDelayedMessage(mainHandler, 1)
Log.d("MainActivity","testSendDelayedMessages() end ")
}
28:58.186 發(fā)送 Message,29:00.690 Message 執(zhí)行匕坯,時(shí)間差為 2504ms束昵,并非準(zhǔn)確的 2500ms。
09-22 22:28:57.964 24980 24980 D MainActivity: onCreate()
09-22 22:28:58.186 24980 24980 D MainActivity: testSendDelayedMessages() start
// 發(fā)送 Message
09-22 22:28:58.186 24980 24980 D MainActivity: testSendDelayedMessages() end
// Message 執(zhí)行
09-22 22:29:00.690 24980 24980 D MainActivity: Main thread message occurred & what:1
如果連續(xù)發(fā)送 10 個(gè)均延時(shí) 2500ms 的 Message 會(huì)怎么樣葛峻?
private fun testSendDelayedMessages() {
Log.d("MainActivity","testSendDelayedMessages() start")
// 連續(xù)發(fā)送 10 個(gè) Delay 2500 ms 的 Message
for (i in 1..10) {
sendDelayedMessage(mainHandler, i)
}
Log.d("MainActivity","testSendDelayedMessages() end ")
}
第 1 個(gè) Message 執(zhí)行的時(shí)間差為 2505ms(39:56.841 - 39:54.336)昙啄,第 10 個(gè) Message 執(zhí)行的時(shí)間差已經(jīng)達(dá)到了 2508ms(39:56.844 - 39:54.336)。
09-22 22:39:54.116 25104 25104 D MainActivity: onCreate()
09-22 22:39:54.336 25104 25104 D MainActivity: testSendDelayedMessages() start
09-22 22:39:54.337 25104 25104 D MainActivity: testSendDelayedMessages() end
09-22 22:39:56.841 25104 25104 D MainActivity: Main thread message occurred & what:1
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:2
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:3
..
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:8
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:9
09-22 22:39:56.844 25104 25104 D MainActivity: Main thread message occurred & what:10
為什么塔沃?
延時(shí) Message 執(zhí)行的時(shí)刻 when 采用的是發(fā)送的時(shí)刻和 Delay
時(shí)長的累加,基于此排隊(duì)進(jìn) MessageQueue轻绞。
// Handler.java
public final boolean postDelayed(Runnable r, int what, long delayMillis) {
return sendMessageDelayed(getPostMessage(r).setWhat(what), delayMillis);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
return enqueueMessage(queue, msg, uptimeMillis);
}
Delay Message 尚未抵達(dá)的時(shí)候,MessageQueue#next()
會(huì)將讀取隊(duì)列的時(shí)刻與 when 的差值佣耐,作為下一次通知 Native 休眠的時(shí)長政勃。進(jìn)行下一次循環(huán)前,next()
還存在其他邏輯兼砖,導(dǎo)致 wake up 的時(shí)刻存在滯后奸远。此外由于 wake up 后線程存在其他 Message 占用,導(dǎo)致執(zhí)行更加延后讽挟。
// MessageQueue.java
Message next() {
...
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
...
if (msg != null) {
// 計(jì)算下一次循環(huán)應(yīng)當(dāng)休眠的時(shí)長
if (now < msg.when) {
nextPollTimeoutMillis
= (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
...
}
} else {
...
}
...
}
...
}
}
結(jié)論
由于喚醒時(shí)長的計(jì)算誤差和回調(diào)的任務(wù)可能占用線程懒叛,導(dǎo)致延時(shí)執(zhí)行 Message 不是時(shí)間到了就會(huì)執(zhí)行,其執(zhí)行的時(shí)刻必然晚于 Delay 的時(shí)刻耽梅。
插隊(duì)執(zhí)行 Message
Handler 還提供了 Message 插隊(duì)的 API:sendMessageAtFrontOfQueue()
和 postAtFrontOfQueue()
薛窥。
在上述的 send 和 post 之后同時(shí)調(diào)用 xxxFrontOfQueue 的方法,Message 的執(zhí)行結(jié)果會(huì)怎么樣眼姐?
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
testSendNoDelayedMessages()
testFrontMessages() // 立馬調(diào)用 FrontOfQueue 的方法
}
分別調(diào)用 sendMessageAtFrontOfQueue() 和 postAtFrontOfQueue() 的 API诅迷。
private fun testFrontMessages() {
Log.d("MainActivity","testFrontMessages() start")
testSendFrontMessages()
testPostFrontRunnable()
Log.d("MainActivity","testFrontMessages() end ")
}
private fun testSendFrontMessages() {
Log.d("MainActivity","testSendFrontMessages() start")
for (i in 21..30) {
sendMessageFront(mainHandler, i)
}
Log.d("MainActivity","testSendFrontMessages() end ")
}
private fun testPostFrontRunnable() {
Log.d("MainActivity","testPostFrontRunnable() start")
for (i in 31..40) {
mainHandler.postAtFrontOfQueue() { Log.d("MainActivity", "testPostFrontRunnable() run & i:${i}") }
}
Log.d("MainActivity","testPostFrontRunnable() end ")
}
當(dāng)主線程的打印日志按序輸出后,Message 開始逐個(gè)執(zhí)行众旗。按照預(yù)想的一樣罢杉,F(xiàn)rontOfQueue 的 Message 會(huì)先執(zhí)行,也就是最后一次調(diào)用這個(gè) API 的最早回調(diào)逝钥。
Front 的 Message 逆序執(zhí)行完畢之后屑那,普通的 Message 才按照請(qǐng)求的順序執(zhí)行。
D MainActivity: testSendNoDelayedMessages() start
D MainActivity: startSendMessage() start
D MainActivity: startSendMessage() end
D MainActivity: testPostRunnable() start
D MainActivity: testPostRunnable() end
D MainActivity: testSendNoDelayedMessages() end
D MainActivity: testFrontMessages() start
D MainActivity: testSendFrontMessages() start
D MainActivity: testSendFrontMessages() end
D MainActivity: testPostFrontRunnable() start
D MainActivity: testPostFrontRunnable() end
D MainActivity: testFrontMessages() end
D MainActivity: testPostFrontRunnable() run & i:40
...
D MainActivity: testPostFrontRunnable() run & i:31
D MainActivity: Main thread message occurred & what:30
...
D MainActivity: Main thread message occurred & what:21
D MainActivity: Main thread message occurred & what:1
...
D MainActivity: Main thread message occurred & what:10
D MainActivity: testPostRunnable() run & i:11
...
D MainActivity: testPostRunnable() run & i:20
怎么實(shí)現(xiàn)的艘款?
原理在于 sendMessageAtFrontOfQueue() 或 postAtFrontOfQueue() 發(fā)送的 Mesage 被記錄的 when 屬性被固定為 0持际。
// Handler.java
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
return enqueueMessage(queue, msg, 0); // 發(fā)送的 when 等于 0
}
public final boolean postAtFrontOfQueue(@NonNull Runnable r) {
return sendMessageAtFrontOfQueue(getPostMessage(r));
}
從入隊(duì)函數(shù)可以看出,when 為 0 的 Message 會(huì)立即插入隊(duì)首哗咆,所以總會(huì)先得到執(zhí)行蜘欲。
// MessageQueue.java
enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
// 如果 Message 需要插隊(duì)(sendMessageAtFrontOfQueue)
// 則插入隊(duì)首
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
} else {
...
}
...
}
return true;
}
結(jié)論
sendMessageAtFrontOfQueue() 和 postAtFrontOfQueue() 的 API 通過將 when 預(yù)設(shè)為 0 進(jìn)而插入 Message 至隊(duì)首,最終達(dá)到 Message 先得到執(zhí)行的目的晌柬。
但需要注意的是姥份,這將造成本來先該執(zhí)行的 Message 被延后調(diào)度,對(duì)于存在先后關(guān)系的業(yè)務(wù)邏輯來說將可能造成順序問題年碘,謹(jǐn)慎使用澈歉!
異步執(zhí)行 Message
Handler 發(fā)送的 Message 都是同步
的,意味著大家都按照 when 的先后進(jìn)行排序屿衅,誰先到誰執(zhí)行埃难。
如果遇到優(yōu)先級(jí)高的 Message 可以通過 FrontQueue 發(fā)送插隊(duì) Message即可。但如果是希望同步的隊(duì)列停滯只執(zhí)行指定 Message 的話,即 Message 異步
執(zhí)行涡尘,現(xiàn)有的 API 是不夠的忍弛。
事實(shí)上 Android 提供了同步屏障
的機(jī)制來實(shí)現(xiàn)這一需求,不過主要面向的是系統(tǒng) App 或 系統(tǒng)考抄,App 可以通過反射來使用细疚。
通過異步 Handler 實(shí)現(xiàn)
除了一般使用的 Handler 構(gòu)造函數(shù)以外,Handler 還提供了創(chuàng)建發(fā)送異步 Message 的專用構(gòu)造函數(shù)川梅。通過該 Handler 發(fā)送的 Message 或 Runnable 都是異步的疯兼。我們將其稱為異步 Handler
。
// Handler.java
@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
@NonNull
public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {
if (looper == null) throw new NullPointerException("looper must not be null");
if (callback == null) throw new NullPointerException("callback must not be null");
return new Handler(looper, callback, true);
}
我們啟動(dòng)一個(gè) HandlerThread
來測試一下同步屏障的使用:分別構(gòu)建一個(gè)普通 Handler
和 異步 Handler
挑势。
private fun startBarrierThread() {
val handlerThread = HandlerThread("Test barrier thread")
handlerThread.start()
normalHandler = Handler(handlerThread.looper) { msg ->
Log.d(...)
true
}
barrierHandler = Handler.createAsync(handlerThread.looper) { msg ->
Log.d(...)
true
}
}
啟動(dòng) HandlerThread 并向兩個(gè) Handler 各發(fā)送一個(gè) Message镇防。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
startBarrierThread()
testNormalMessage()
testSyncBarrierByHandler()
}
private fun testNormalMessage() {
sendMessageRightNow(normalHandler, 1)
}
private fun testSyncBarrierByHandler() {
sendMessageRightNow(barrierHandler, 2)
}
是異步 Handler 的 Message 先執(zhí)行嗎?非也潮饱,因?yàn)槲覀冞€沒有通知 MessageQueue 建立同步屏障!
09-24 23:02:19.032 28113 28113 D MainActivity: onCreate()
09-24 23:02:19.150 28113 28141 D MainActivity: Normal handler message occurred & what:1
09-24 23:02:19.150 28113 28141 D MainActivity: Barrier handler message occurred & what:2
除了發(fā)送異步Handler 發(fā)送異步 Message 以外诫给,需要通過反射
事先建立起同步屏障香拉。
注意:建立同步屏障必須早于需要屏蔽的同步 Message,否則無效中狂,后面的原理會(huì)提及凫碌。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
startBarrierThread()
// 建立一個(gè)同步屏障
postSyncBarrier(barrierHandler.looper)
testNormalMessage()
testSyncBarrierByHandler()
}
private fun postSyncBarrier(looper: Looper) {
Log.d(...)
val method: Method = MessageQueue::class.java.getDeclaredMethod("postSyncBarrier")
barrierToken = method.invoke(looper.queue) as Int
}
這樣子便可以看到,異步 Message 執(zhí)行了胃榕,而且同步 Message 永遠(yuǎn)得不到執(zhí)行盛险。
09-24 23:11:36.176 28600 28600 D MainActivity: onCreate()
09-24 23:11:36.296 28600 28600 D MainActivity: Add sync barrier
09-24 23:11:36.300 28600 28629 D MainActivity: Barrier handler message occurred & what:2
原因在于建立的同步屏障尚未移除,永遠(yuǎn)只處理隊(duì)列里的異步 Message勋又。想要讓同步 Message 恢復(fù)執(zhí)行的話 remove 同步屏障即可苦掘,同樣也需要反射!
我們?cè)诋惒?Handler 執(zhí)行結(jié)束后移除同步屏障楔壤。
private fun startBarrierThread() {
...
barrierHandler = Handler.createAsync(handlerThread.looper) { msg ->
Log.d(...)
// 移除同步屏障
removeSyncBarrier(barrierHandler.looper)
true
}
}
fun removeSyncBarrier(looper: Looper) {
Log.d(...)
val method = MessageQueue::class.java
.getDeclaredMethod("removeSyncBarrier", Int::class.javaPrimitiveType)
method.invoke(looper.queue, barrierToken)
}
可以看到同步 Message 恢復(fù)了鹤啡。
09-24 23:10:31.533 28539 28539 D MainActivity: onCreate()
09-24 23:10:31.652 28539 28568 D MainActivity: Barrier handler message occurred & what:2
09-24 23:10:31.652 28539 28568 D MainActivity: Remove sync barrier
09-24 23:10:31.653 28539 28568 D MainActivity: Normal handler message occurred & what:1
通過異步 Message 實(shí)現(xiàn)
沒有專用的異步 Handler 的時(shí)候,可以向普通 Handler 發(fā)送一個(gè) isAsync
屬性為 true 的 message蹲嚣,效果和異步 Handler 是一樣的递瑰。當(dāng)然這種方式仍舊需要建立同步屏障。
在原有的發(fā)送 Message 的函數(shù)里加入 isAsync 的重載參數(shù)隙畜。
private fun sendMessageRightNow(handler: Handler, what: Int, isAsync: Boolean = false) {
Message.obtain().let {
it.what = what
it.isAsynchronous = isAsync
handler.sendMessage(it)
}
}
向普通 Handler 發(fā)送異步 Message抖部。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
testNormalMessage()
// 改用 Message 方式發(fā)送異步 Message
testSyncBarrierByMessage()
}
private fun testSyncBarrierByMessage() {
sendMessageRightNow(normalHandler, 2, true)
}
同樣記得在異步 Message 收到后移除同步屏障。
private fun startBarrierThread() {
...
normalHandler = Handler(handlerThread.looper) { msg ->
Log.d(...)
if (2 == msg.what) removeSyncBarrier(barrierHandler.looper)
true
}
}
結(jié)果和異步 Handler 的方式一致议惰。
09-24 23:58:05.801 29040 29040 D MainActivity: onCreate()
09-24 23:58:05.923 29040 29040 D MainActivity: Add sync barrier
09-24 23:58:05.923 29040 29070 D MainActivity: Normal handler message occurred & what:2
09-24 23:58:05.924 29040 29070 D MainActivity: Remove sync barrier
09-24 23:58:05.924 29040 29070 D MainActivity: Normal handler message occurred & what:1
原理
先來看一下同步屏障是怎么建立的慎颗。
// MessageQueue.java
// 默認(rèn)是調(diào)用的時(shí)刻開始建立屏障
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
// 同步屏障支持指定開始的時(shí)刻
// 默認(rèn)是調(diào)用的時(shí)刻,而 0 表示?
private int postSyncBarrier(long when) {
synchronized (this) {
// 同步屏障可以建立多個(gè)哗总,用計(jì)數(shù)的 Token 變量識(shí)別
final int token = mNextBarrierToken++;
// 獲取一個(gè)屏障 Message
// 其 target 屬性為空
// 指定 when 屬性為屏障的開始時(shí)刻
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
// 將 Token 存入屏障 Message
// 用以識(shí)別對(duì)應(yīng)的同步屏障
msg.arg1 = token;
// 按照 when 的先后
// 找到屏障 Message 插入隊(duì)列的適當(dāng)位置
// 所以几颜,如果同步屏障的建立調(diào)用得晚
// 那么在它之前的 Message 無法阻攔
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
// 將屏障 Message 插入
if (prev != null) {
msg.next = p;
prev.next = msg;
} else {
// 如果隊(duì)列尚無 Message
// 或隊(duì)首的 Message 時(shí)刻
// 都比屏障 Message 要晚的話
// 將屏障 Message 插入隊(duì)首
msg.next = p;
mMessages = msg;
}
// 返回上面的 Token 給調(diào)用端
// 主要用于移除對(duì)應(yīng)的屏障
return token;
}
}
再來看下異步 Message 如何執(zhí)行。
// MessageQueue.java
Message next() {
...
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 隊(duì)首是屏障 Message 的話
// 遍歷找到下一個(gè)異步 Message
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
// 沒有建立同步屏障且隊(duì)里有 Message
// 或者
// 建立了同步屏障下且找到了異步 Message
if (msg != null) {
// 如果當(dāng)前時(shí)間尚早于目標(biāo)執(zhí)行時(shí)刻
if (now < msg.when) {
// 更新下次循環(huán)應(yīng)當(dāng)休眠的超時(shí)時(shí)間
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
// Message 找到了并出隊(duì)
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
// Message 返回
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 隊(duì)里尚無 Message
// 或建立了同步屏障讯屈,但尚無異步 Message
// 無限休眠
nextPollTimeoutMillis = -1;
}
...
}
...
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
最后看一下同步屏障如何移除蛋哭。
// MessageQueue.java
// 需要傳入 add 時(shí)返回的 Token
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
// 遍歷隊(duì)列直到找到 token 吻合的屏障 Message
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
// 如果沒找到會(huì)拋出異常
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
// 將屏障 Message 移除
// 如果屏障 Message 不在隊(duì)首的話
// 無需喚醒
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
// 屏障 Message 在隊(duì)首
// 且新的隊(duì)首存在且不是另一個(gè)屏障的話
// 需要立即喚醒
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// 喚醒以立即處理后面的 Message
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
對(duì)原理進(jìn)行簡單的總結(jié):
- 同步屏障的建立:按照調(diào)用的時(shí)刻 when 在合適的位置放入一個(gè)屏障 Message(
target
屬性為 null)來實(shí)現(xiàn),同時(shí)得到標(biāo)識(shí)屏障的計(jì)數(shù)token
存入屏障 Message - 讀取隊(duì)列的時(shí)候發(fā)現(xiàn)存在屏障 Message 的話涮母,會(huì)遍歷隊(duì)列并返回最早執(zhí)行的異步 Message
- 同步屏障的移除:按照 token 去隊(duì)列里找到匹配的屏障 Message 進(jìn)行出隊(duì)操作谆趾,如果出隊(duì)后隊(duì)首存在 Message 且非另一個(gè)同步屏障的話,立即喚醒
looper 線程
結(jié)論和應(yīng)用
結(jié)論:
- 可以通過異步 Handler叛本,也可以通過異步 Message 兩種方式向 MessageQueue 添加異步 Message
- 但都需要事先建立同步屏障沪蓬,屏障的建立時(shí)間必須在阻攔的 Message 發(fā)出之前
- 可以建立多個(gè)同步屏障,將按照指定的時(shí)刻排隊(duì)来候,通過計(jì)數(shù) Token 進(jìn)行識(shí)別
- 同步屏障使用完之后記得移除跷叉,否則后續(xù)的 Message 永遠(yuǎn)阻塞
和插隊(duì)執(zhí)行 Message 的區(qū)別:
- 插隊(duì) Message 只能確保先執(zhí)行,完了后續(xù)的 Message 還得執(zhí)行
- 異步 Message 則不同营搅,同步屏障一旦建立將保持休眠云挟,直到異步 Message 抵達(dá)。只有同步屏障被撤銷转质,后續(xù) Message 才可恢復(fù)執(zhí)行
應(yīng)用:
AOSP 系統(tǒng)中使用異步 Message 最典型的地方要屬屏幕刷新园欣,刷新任務(wù)的 Message 不希望被主線程的 Message 隊(duì)列阻塞,所以在發(fā)送刷新 Message 之前都會(huì)建立一個(gè)同步屏障休蟹,確保刷新任務(wù)優(yōu)先執(zhí)行沸枯。
// ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
屏障建立之后發(fā)送異步 Message。
// Choreographer.java
private void postCallbackDelayedInternal(...) {
synchronized (mLock) {
...
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
空閑執(zhí)行 “Message”
MessageQueue 提供的 IdleHandler
可以讓隊(duì)列在空閑的時(shí)候回調(diào)(queueIdle()
)指定的邏輯赂弓,它本質(zhì)上不是 Message 類型绑榴,但它在 MessageQueue 里調(diào)度的時(shí)候類似于 Message 的邏輯,姑且將它也理解成一種特殊的 ”Message“拣展。
使用上很簡單彭沼,調(diào)用 MessageQueue 的 addIdleHandler()
添加實(shí)現(xiàn)即可,執(zhí)行完之后無需再度執(zhí)行的話需要調(diào)用 removeIdleHandler()
移除备埃,或在回調(diào)里返回 false姓惑。
override fun onCreate(savedInstanceState: Bundle?) {
...
testIdleHandler()
}
private fun testIdleHandler() {
Log.d("MainActivity","testIdleHandler() start")
mainHandler.looper.queue.addIdleHandler {
Log.d("MainActivity", "testIdleHandler() queueIdle callback")
false
}
Log.d("MainActivity","testIdleHandler() end ")
}
可以看到 addIdleHandler 調(diào)用之后并沒有立即執(zhí)行,而是過了幾百 ms按脚,queueIdle() 才得到了執(zhí)行于毙。
09-23 22:56:46.130 7732 7732 D MainActivity: onCreate()
09-23 22:56:46.281 7732 7732 D MainActivity: testIdleHandler() start
09-23 22:56:46.281 7732 7732 D MainActivity: testIdleHandler() end
09-23 22:56:46.598 7732 7732 D MainActivity: testIdleHandler() queueIdle callback
如果在 addIdleHandler 調(diào)用之后接著發(fā)送一串非延時(shí) Message,queueIdle() 是先執(zhí)行還是后執(zhí)行呢辅搬?
override fun onCreate(savedInstanceState: Bundle?) {
...
testIdleHandler()
testSendMessages()
}
結(jié)果顯示一堆 Message 執(zhí)行完了之后唯沮,仍舊過了幾百 ms脖旱,queueIdle() 才得到了執(zhí)行。
09-23 23:07:50.639 7926 7926 D MainActivity: onCreate()
09-23 23:07:50.856 7926 7926 D MainActivity: testIdleHandler() start
09-23 23:07:50.856 7926 7926 D MainActivity: testIdleHandler() end
09-23 23:07:50.856 7926 7926 D MainActivity: startSendMessage() start
09-23 23:07:50.857 7926 7926 D MainActivity: startSendMessage() end
09-23 23:07:50.914 7926 7926 D MainActivity: Main thread message occurred & what:1
...
09-23 23:07:50.916 7926 7926 D MainActivity: Main thread message occurred & what:10
09-23 23:07:51.132 7926 7926 D MainActivity: testIdleHandler() queueIdle callback
上述結(jié)果也可以理解介蛉,MessageQueue 里仍有一堆 Message 等待處理萌庆,并非空閑狀態(tài)。所以需要執(zhí)行完之后才有機(jī)會(huì)回調(diào) queueIdle() 币旧。
那如果發(fā)送的是延時(shí) Message 呢践险?
override fun onCreate(savedInstanceState: Bundle?) {
...
testIdleHandler()
testSendDelayedMessages()
}
因?yàn)榘l(fā)送的是延時(shí) Message,MessageQueue 暫時(shí)是空閑的吹菱,會(huì)先將 IdleHandler 取出來處理巍虫。
09-23 23:21:36.135 8161 8161 D MainActivity: onCreate()
09-23 23:21:36.339 8161 8161 D MainActivity: testIdleHandler() start
09-23 23:21:36.340 8161 8161 D MainActivity: testIdleHandler() end
09-23 23:21:36.340 8161 8161 D MainActivity: testSendDelayedMessages() start
09-23 23:21:36.340 8161 8161 D MainActivity: testSendDelayedMessages() end
09-23 23:21:36.729 8161 8161 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:21:38.844 8161 8161 D MainActivity: Main thread message occurred & what:1
...
09-23 23:21:38.845 8161 8161 D MainActivity: Main thread message occurred & what:10
上面的 queueIdle() 返回了 false 確保處理后 Handler 得到了移除。
但如果返回 true 且沒有調(diào)用 removeIdleHandler() 的話鳍刷,后續(xù)空閑的時(shí)候 Handler 還會(huì)被執(zhí)行占遥,這點(diǎn)需要留意!
private fun testIdleHandler() {
mainHandler.looper.queue.addIdleHandler {
...
true // false
}
}
queueIdle() 因?yàn)闆]被移除的緣故被回調(diào)了多次输瓜,源自于 Looper 沒執(zhí)行完一次 Message 后發(fā)現(xiàn)尚無 Message 的時(shí)候都會(huì)回調(diào)一遍 IdleHandler瓦胎,直到隊(duì)列一直沒有 Message 到來。
09-23 23:24:04.765 8226 8226 D MainActivity: onCreate()
09-23 23:24:05.010 8226 8226 D MainActivity: testIdleHandler() start
09-23 23:24:05.011 8226 8226 D MainActivity: testIdleHandler() end
09-23 23:24:05.368 8226 8226 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:24:05.370 8226 8226 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:24:05.378 8226 8226 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:24:05.381 8226 8226 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:24:05.459 8226 8226 D MainActivity: testIdleHandler() queueIdle callback
那如果 add 完不移除的 IdleHandler 之后尤揣,發(fā)送一個(gè)延時(shí) Message凛捏,那便會(huì)導(dǎo)致空閑消息多執(zhí)行一遍。
override fun onCreate(savedInstanceState: Bundle?) {
...
testIdleHandler()
sendDelayedMessage(mainHandler, 1)
}
09-23 23:31:53.928 8620 8620 D MainActivity: onCreate()
09-23 23:31:54.042 8620 8620 D MainActivity: testIdleHandler() start
09-23 23:31:54.042 8620 8620 D MainActivity: testIdleHandler() end
09-23 23:31:54.272 8620 8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:54.273 8620 8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:54.278 8620 8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:54.307 8620 8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:54.733 8620 8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:56.546 8620 8620 D MainActivity: Main thread message occurred & what:1
09-23 23:31:56.546 8620 8620 D MainActivity: testIdleHandler() queueIdle callback
為什么芹缔?
queueIdle() 的回調(diào)由 MessageQueue#next() 回調(diào)。
// MessageQueue.java
Message next() {
...
// 循環(huán)的初次將待處理 IdleHandler 計(jì)數(shù)置為 -1
// 保證第一次可以檢查 Idle Handler 的存在和調(diào)用
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 隊(duì)首的 Message 且建立了同步屏障的話瓶盛,尋找下一個(gè)異步 Message
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
// 找到了合適的 Message
if (msg != null) {
// 如果當(dāng)前時(shí)間尚早于目標(biāo)執(zhí)行時(shí)刻
// 設(shè)置休眠的超時(shí)時(shí)間最欠,即當(dāng)前時(shí)間與目標(biāo)時(shí)刻的差值
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
// 時(shí)間條件滿足 Message 出隊(duì)
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
// 并返回 Message
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 隊(duì)里尚無合適的 Message
// 進(jìn)入無限休眠
nextPollTimeoutMillis = -1;
}
// 如果正在退出 Looper,結(jié)束循環(huán)并返回 null
// 將促使 loop() 退出
if (mQuitting) {
dispose();
return null;
}
// 如果沒有合適的 Message 且 Looper 沒有退出
// 檢查是否有 Idle Handler 需要處理
// 讀取 Idle Handler 列表
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 如果暫時(shí)沒有 Idle Handler 需要處理惩猫,則進(jìn)入下一次循環(huán)
// 為使下次循環(huán)如果出現(xiàn)新的 Idle Handler 能有機(jī)會(huì)執(zhí)行
// 不重置計(jì)數(shù)器芝硬,仍為初始值 -1
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
// 如果 IdleHandler 存在則拷貝到待處理列表
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 遍歷待處理 Idle Handlers
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null;
boolean keep = false;
try {
// 逐個(gè)回調(diào) queueIdle()
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
// 回調(diào)返回 false,則將其移除出 Idle 列表
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 處理完之后重置 IdleHandler 的計(jì)數(shù)
// 保證下次循環(huán)不會(huì)重復(fù)處理 IdleHandler
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
有幾點(diǎn)細(xì)節(jié)需要留意:
- next() 循環(huán)的第一次將 count 置為
-1
轧房,確保隊(duì)列空閑的時(shí)候必然有機(jī)會(huì)處理 IdleHandler - 如果暫無 IdleHandler 可以處理直接進(jìn)入下一次循環(huán)拌阴,并且保留 count 的處置,確保下次循環(huán)可以檢查是否有新的 IdleHandler 加入進(jìn)來
- IdleHandler 正常處理結(jié)束之后奶镶,避免下次循環(huán)重復(fù)處理迟赃,會(huì)將 count 置為
0
,保證下次不再檢查厂镇。注意:是下次循環(huán)纤壁,不是永久不檢查
結(jié)論和應(yīng)用
結(jié)論: IdleHandler 可以實(shí)現(xiàn) MessageQueue 空閑狀態(tài)下的任務(wù)執(zhí)行,比如做一些啟動(dòng)時(shí)的輕量級(jí)初始化任務(wù)捺信。但由于其執(zhí)行的時(shí)機(jī)依賴于隊(duì)列的 Message 狀態(tài)酌媒,不太可控,謹(jǐn)慎使用!
應(yīng)用:AOSP
源碼里有不少地方使用了 IdleHandler 機(jī)制秒咨,比如 ActivityThread
使用它在空閑的狀態(tài)下進(jìn)行 GC
回收處理喇辽。
// ActivityThread.java
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
purgePendingResources();
return false;
}
}
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
Looper.myQueue().addIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
void unscheduleGcIdler() {
if (mGcIdlerScheduled) {
mGcIdlerScheduled = false;
Looper.myQueue().removeIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
名詞回顧
非延時(shí)、延時(shí)以及插隊(duì)執(zhí)行這幾種 Message 大家使用較多雨席,無需贅述菩咨。但其他幾個(gè)冷僻的 Message 術(shù)語需要總結(jié)一下,供大家快速對(duì)比和加深理解舅世。
Message 術(shù)語 | 介紹 |
---|---|
異步 Message | isAsync 屬性為 true 需要異步執(zhí)行的 Message旦委,需要配合同步屏障使用 |
異步 Handler | 專門發(fā)送異步 Message 的 Handler |
屏障 Message | target 為空并持有 token 信息的 Message 實(shí)例放入隊(duì)列,作為同步屏障的起點(diǎn) |
同步屏障 | 在 MessageQueue 指定時(shí)刻插入屏障 Message 確保只有異步 Message 執(zhí)行的機(jī)制 |
空閑 IdleHandler | 用于在 MessageQueue 空閑的時(shí)候回調(diào)的處理接口雏亚,若不移除每次隊(duì)列空閑了均會(huì)執(zhí)行 |
結(jié)語
上述對(duì)于各種 Message 和 IdleHandler 做了演示和原理闡述缨硝,相信對(duì)于它的細(xì)節(jié)有了更深的了解。
下面來進(jìn)行一個(gè)簡單的總結(jié):
-
非延時(shí)
執(zhí)行 Message:并非立即執(zhí)行罢低,而是按照請(qǐng)求的時(shí)刻進(jìn)行排隊(duì)和調(diào)度查辩,最終取決于隊(duì)列的順序和主線程是否空閑 -
延時(shí)
執(zhí)行 Message:也并非在 Delay 的時(shí)刻立即執(zhí)行,執(zhí)行時(shí)刻受喚醒誤差和線程任務(wù)阻塞的影響必然晚于 Delay 時(shí)刻 -
插隊(duì)
執(zhí)行 Mesage:同樣并非立即執(zhí)行网持,而是每次都將任務(wù)放在了隊(duì)首宜岛,達(dá)到先執(zhí)行的目的,但打亂了執(zhí)行順序存在邏輯隱患 -
異步
Message:系統(tǒng)使用居多功舀,App 則需反射萍倡,通過這種機(jī)制可插隊(duì)執(zhí)行同時(shí)確保其他 Message 阻塞,學(xué)習(xí)一下 -
IdleHandler
“Message”:系統(tǒng)多有使用辟汰,實(shí)現(xiàn) MessageQueue 空閑狀態(tài)下的任務(wù)執(zhí)行列敲,但執(zhí)行時(shí)機(jī)不可控,最好執(zhí)行完之后便移除帖汞,謹(jǐn)慎使用