MessageQueue消息入队
本文承接Handler消息发送和Looper主循环。前文已经说明 Handler 最终调用队列入队;这里只追踪这个入队点怎样改变链表,以及为什么“插到队头”不等于“任何情况下都立刻唤醒”。
1. 入队前提
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
}
}入队前有三道边界:屏障 Message 没有 target,不能走普通入队;已经在队列或正在使用的 Message 不能再次发送;队列退出后,发送失败并回收 Message。只有通过这些检查,队列才会标记消息在用并写入绝对执行时间。
2. 队头插入
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:enqueueMessage()
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
if (p == null) {
mLast = mMessages;
}
}空队列、when == 0 的队头发送,或新时间早于当前队头时,消息直接成为新头节点。只有 Looper 当前确实阻塞在 native poll(mBlocked)时才需要唤醒;如果 Looper 正在执行另一条消息,入队不会额外唤醒它。
3. 中间插入
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:enqueueMessage()
else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
if (when >= mLast.when) {
needWake = needWake && mAsyncMessageCount == 0;
msg.next = null;
mLast.next = msg;
mLast = msg;
} else {
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
if (p == null) {
mLast = msg;
}
msg.next = p;
prev.next = msg;
}
}普通延迟消息按 when 升序插入,when 相等时沿已有顺序向后走。尾插入和中间插入通常不用唤醒,因为队头等待时间没有变;例外是队头为同步屏障、Looper 正在阻塞,而新异步消息可能成为屏障后的可执行消息。
4. 异步计数
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:enqueueMessage()、next()
if (msg.isAsynchronous()) {
mAsyncMessageCount++;
}
if (needWake) {
nativeWake(mPtr);
}mAsyncMessageCount 是队列维护的异步消息数量,用来判断尾部插入是否可能改变屏障等待。它不是优先级数值,也不会改变链表的 when 排序;next() 真正取出异步消息时会递减该计数。
5. 同步屏障
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:postSyncBarrier()、removeSyncBarrier()
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
// 按 when 插入,target 保持 null
return token;
}
}屏障本身也是链表节点,但没有 target,所以不经过普通 enqueueMessage()。next() 遇到它后会跳过后面的同步消息,寻找异步消息;入队逻辑因此必须在屏障位于队头且新消息异步时唤醒等待线程。屏障移除后,如果队头恢复为普通消息,removeSyncBarrier() 也会按需唤醒。
6. 生效时机
入队的锁内动作顺序是:校验 → 标记 in-use → 写 when → 链表插入 → 更新异步计数 → 必要时 native 唤醒。调用者看到 true 时,这些动作已经完成;但消息仍可能等待到期、被屏障阻塞或在退出清理中回收。nativeWake() 只负责让 Looper 重新检查,真正出队仍由 MessageQueue.next() 决定。
7. 失败边界
| 输入状态 | 行为 | Message结果 |
|---|---|---|
target == null | 抛 IllegalArgumentException | 未进入队列 |
isInUse() == true | 抛 IllegalStateException | 保持原使用状态 |
mQuitting == true | 记录警告并返回 false | msg.recycle() |
| 合法消息 | 返回 true | 标记 in-use 并进入链表 |
特别要注意:退出后的失败路径使用 msg.recycle(),而已在队列中的消息会由退出清理调用 recycleUnchecked()。两者都回收对象,但触发条件和调用者不同。
8. 测试输入
源码文件:frameworks/base/tests/testables/tests/src/android/testing/TestableLooperTest.java
相关函数:testDelayedMessageDoesntSend()、testMessageSendsAfterDelay()
handler.sendMessageDelayed(messageA, 0);
handler.sendMessageDelayed(messageB, 0);
handler.sendMessageDelayed(messageC, 500);
mTestableLooper.processAllMessages();
inOrder.verify(handler).dispatchMessage(messageA);
inOrder.verify(handler).dispatchMessage(messageB);
verify(handler, never()).dispatchMessage(messageC);测试用两条立即消息和一条 500ms 延迟消息,先不推进虚拟时间,断言前两条按顺序分发、第三条不分发;另一测试推进 500ms 后再处理,才断言第三条出现。它验证的是 when 排序和到期门槛,不等价于真实设备上的 native 阻塞精度。
9. 动手验证
rg -n "boolean enqueueMessage|needWake|mAsyncMessageCount|nativeWake" \
frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
rg -n "sendMessageDelayed|testDelayedMessageDoesntSend|testMessageSendsAfterDelay" \
frameworks/base/tests/testables/tests/src/android/testing/TestableLooperTest.java读源码时可以在纸上画出三种队头:普通消息、同步屏障、空队列。再分别放入 when=0、更早时间、较晚时间和异步消息,逐项判断是否改变队头、是否改变可执行消息、是否需要 nativeWake()。
10. 边界说明
本文不展开 Handler 如何生成 when,也不展开 next() 的 IdleHandler、native epoll 和完整同步屏障生命周期。这里的“按时间排序”描述的是 Java 链表插入,不代表消息一定按发送线程的墙上时钟执行;源码使用的是 SystemClock.uptimeMillis() 体系。
