我们会经常使用 Handler 的 send 或 post 去安排一个延时、非延时或插队执行的 Message。但对于这个 Message 到底什么时候执行以及为什么是这样,鲜少细究过。
本文将一 一盘点并起底个中原理!
同时针对大家不太熟悉的异步 Message 和 IdleHandler,进行演示和原理普及,篇幅较大,慢慢享用。
非延时执行 Message
先在主线程创建一个 Handler 并复写 Callback 处理。
private val mainHandler = Handler(Looper.getMainLooper()) { msg -> Log.d( "MainActivity", "Main thread message occurred & what:${msg.what}" ) true }
不断地发送期望即刻执行的 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 ") }
什么时候执行?
公布下日志前,大家可以猜测下运行的结果,Message 或 Runnable 在 send 或 post 之后会否立即执行。不是的话,什么时候执行?
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
答案可能跟预想的略有出入,但一细想好像又是合理的:发送完的 Message 或 Runnable 不会立即执行,MessageQueue 的唤醒和回调需要等主线程的其他工作完成之后才能执行。
为什么?
非延时的 sendMessage()
和 post()
最终仍然是调用 sendMessageAtTime()
将 Message 放入了 MessageQueue
。只不过它的期待执行时间 when
变成了 SystemClock.uptimeMillis()
,即调用的时刻。
// 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 等于当前时刻 } public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { return enqueueMessage(queue, msg, uptimeMillis); }
这些 Message 会按照 when 的先后排队进入 MessageQueue 中,当 Message 满足了条件会立即调用 wake
,反之只是插入队列而已。所以,上述的 send 或 post 循环,会按照调用的先后挨个进入队列,第一个 Message 会触发 wake。
// MessageQueue.java boolean enqueueMessage(Message msg, long when) { ... // 鉴于多线程往 Handler 里发送 Message 的情况 // 在向队列插入 Message 前需要上锁 synchronized (this) { ... msg.markInUse(); // Message 标记正在使用 msg.when = when; // 更新 when 属性 Message p = mMessages; // 拿到队列的 Head boolean needWake; // 如果队列为空 // 或者 Message 需要插队(sendMessageAtFrontOfQueue) // 又或者 Message 执行时刻比 Head 的更早 // 该 Message 插入到队首 if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; // 线程是否因为没有可执行的 Message 正在 block 或 wait // 是的话,唤醒 needWake = mBlocked; } else { // 如果队列已有 Message,Message 优先级又不高,同时执行时刻并不早于队首的 Message // 如果线程正在 block 或 wait,或建立了同步屏障(target 为空),并且 Message 是异步的,则唤醒 needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; // 遍历队列,找到 Message 目标插入位置 for (;;) { prev = p; p = p.next; // 如果已经遍历到队尾了,或 Message 的时刻比当前 Message 要早 // 找到位置了,退出遍历 if (p == null || when < p.when) { break; } // 如果前面决定需要唤醒,但队列已有执行时刻更早的异步 Message 的话,先不唤醒 if (needWake && p.isAsynchronous()) { needWake = false; } } // 将 Message 插入队列的目标位置 msg.next = p; prev.next = msg; } // 需要唤醒的话,唤醒 Native 侧的 MessageQueue if (needWake) { nativeWake(mPtr); } } return true; }
总结来讲:
第一次 send 的 Message 在 enqueue 进 MessageQueue 的队首后,通知 Native 侧 wake
后续发送的其他 Message 或 Runnable 挨个 enqueue 进队列
接着执行主线程的其他 Message,比如日志的打印
空闲后 wake 完毕并在 next() 的下一次循环里将队首 Message 移除和返回给 Looper 去回调和执行
之后 loop() 开始读取 MessageQueue 当前队首 Message 的下一次循环,当前时刻必然晚于 send 时候设置的when,所以队列里的 Message 挨个出队和回调
结论
非延时 Message 并非立即执行,只是放入 MessageQueue 等待调度而已,执行时刻不确定。
MessageQueue 会记录请求的时刻,按照时刻的先后顺序进行排队。如果 MessageQueue 中积攒了很多 Message,或主线程被占用的话,Message 的执行会明显晚于请求的时刻。
比如在 onCreate() 里发送 message 的话,你会发现当 onResume() 执行完才会回调你的 Message。原因在于 onCreate() 等操作也是由 Message 触发,其同步处理完 onResume() 之后,才有机会进入下一次循环去读取你的 Message。
延时执行 Message
延时执行的 Message 使用更为常见,那它又是何时执行呢?
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... testSendDelayedMessages() } private fun testSendDelayedMessages() { Log.d("MainActivity","testSendDelayedMessages() start") // 发送 Delay 2500 ms 的 Message sendDelayedMessage(mainHandler, 1) Log.d("MainActivity","testSendDelayedMessages() end ") }
28:58.186 发送 Message,29:00.690 Message 执行,时间差为 2504ms,并非准确的 2500ms。
09-22 22:28:57.964 24980 24980 D MainActivity: onCreate() 09-22 22:28:58.186 24980 24980 D MainActivity: testSendDelayedMessages() start // 发送 Message 09-22 22:28:58.186 24980 24980 D MainActivity: testSendDelayedMessages() end // Message 执行 09-22 22:29:00.690 24980 24980 D MainActivity: Main thread message occurred & what:1
如果连续发送 10 个均延时 2500ms 的 Message 会怎么样?
private fun testSendDelayedMessages() { Log.d("MainActivity","testSendDelayedMessages() start") // 连续发送 10 个 Delay 2500 ms 的 Message for (i in 1..10) { sendDelayedMessage(mainHandler, i) } Log.d("MainActivity","testSendDelayedMessages() end ") }
第 1 个 Message 执行的时间差为 2505ms(39:56.841 - 39:54.336),第 10 个 Message 执行的时间差已经达到了 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
为什么?
延时 Message 执行的时刻 when 采用的是发送的时刻和 Delay 时长的累加,基于此排队进 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 尚未抵达的时候,MessageQueue#next() 会将读取队列的时刻与 when 的差值,作为下一次通知 Native 休眠的时长。进行下一次循环前,next() 还存在其他逻辑,导致 wake up 的时刻存在滞后。此外由于 wake up 后线程存在其他 Message 占用导致执行更加延后。
// MessageQueue.java Message next() { ... for (;;) { ... nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; ... if (msg != null) { // 计算下一次循环应当休眠的时长 if (now < msg.when) { nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { ... } } else { ... } ... } ... } }
结论
由于唤醒时长的计算误差和回调的任务可能占用线程,导致延时执行 Message 不是时间到了就会执行,其执行的时刻必然晚于 Delay 的时刻。
插队执行 Message
Handler 还提供了 Message 插队的 API:sendMessageAtFrontOfQueue() 和 postAtFrontOfQueue()。
在上述的 send 和 post 之后同时调用 xxxFrontOfQueue 的方法,Message 的执行结果会怎么样?
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... testSendNoDelayedMessages() testFrontMessages() // 立马调用 FrontOfQueue 的方法 }
分别调用 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 ") }
当主线程的打印日志按序输出后,Message 开始逐个执行。按照预想的一样,FrontOfQueue 的 Message 会先执行,也就是最后一次调用这个 API 的最早回调。
Front 的 Message 逆序执行完毕之后,普通的 Message 才按照请求的顺序执行。
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
怎么实现的?
原理在于 sendMessageAtFrontOfQueue() 或 postAtFrontOfQueue() 发送的 Mesage 被记录的 when 属性被固定为 0。
// Handler.java public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) { return enqueueMessage(queue, msg, 0); // 发送的 when 等于 0 } public final boolean postAtFrontOfQueue(@NonNull Runnable r) { return sendMessageAtFrontOfQueue(getPostMessage(r)); }
从入队函数可以看出,when 为 0 的 Message 会立即插入队首,所以总会先得到执行。
// MessageQueue.java enqueueMessage(Message msg, long when) { ... synchronized (this) { ... // 如果 Message 需要插队(sendMessageAtFrontOfQueue) // 则插入队首 if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; } else { ... } ... } return true; }
结论
sendMessageAtFrontOfQueue() 和 postAtFrontOfQueue() 的 API 通过将 when 预设为 0 进而插入 Message 至队首,最终达到 Message 先得到执行的目的。
但需要注意的是,这将造成本来先该执行的 Message 被延后调度,对于存在先后关系的业务逻辑来说将可能造成顺序问题,谨慎使用!