万字复盘 Handler 中各式 Message 的使用和原理
Posted TechMerger
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了万字复盘 Handler 中各式 Message 的使用和原理相关的知识,希望对你有一定的参考价值。
我们会经常使用 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 进队列
- 接着执行主线程的其他任务,比如日志的打印
- 空闲后 wake 完毕并在 next() 的下一次循环里将队首 Message 移除和返回给 Looper 去回调和执行
- 之后 loop() 开始读取 MessageQueue 当前队首 Message 的下一次循环,当前时刻必然晚于 send 时候设置的when,所以队列里的 Message 挨个出队和回调
结论
非延时 Message 并非立即执行,只是放入 MessageQueue 等待调度而已,执行时刻不确定。
MessageQueue 会记录请求的时刻,按照时刻的先后顺序进行排队。如果 MessageQueue 中积攒了很多 Message,或主线程被占用的话,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 后线程存在其他任务导致执行更加延后。
// 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 {
..万字复盘 Handler 中各式 Message 的使用和原理
转帖重磅!万字报告复盘半导体产业60年兴衰,中国将腾飞附下载| 智东西内参