MessageQueue取消息
本文承接MessageQueue消息入队和Looper主循环。Looper.loopOnce() 只问队列一个问题:next() 现在能否交付一条 Message?要回答它,next() 必须在 native 等待、Java 链表、同步屏障、退出标志和空闲回调之间反复切换。
1. 两层循环
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:next()
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 检查链表、退出和 IdleHandler
}
}
}外层循环负责“等待后再次观察”;锁内代码只读取和修改队列状态;IdleHandler 则在锁外执行。这样 native 阻塞不会持有 Java 队列锁,用户回调也不会持锁运行,而链表摘除和退出判断仍保持原子性。
2. 等待时长
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:next()
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(
msg.when - now, Integer.MAX_VALUE);
} else {
// 消息已到期,摘除并返回
}
} else {
nextPollTimeoutMillis = -1;
}三种值表达三种状态:0 表示立即检查,正数表示等待到队头消息到期,-1 表示没有消息时无限等待。next() 不在 Java 中睡眠,而是把这个值交给 nativePollOnce();native 返回后仍需重新读取 uptimeMillis(),因为等待期间可能有新消息或唤醒事件。
3. Binder刷出
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:next()
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);只有即将进行非零超时等待时才刷出当前线程待发送的 Binder 命令;timeout=0 会立即返回,不需要在这次短检查前额外刷出。它发生在进入 native poll 之前,属于“可能阻塞前的线程收尾”,不改变 Message 链表的排序。
4. 屏障扫描
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:next()
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 队头是同步屏障,只寻找异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}同步屏障用 target == null 表示。扫描时 prevMsg 记录屏障或其后最后一个被跳过节点;找到异步消息后,后续摘除仍需把它从原链表位置拆下,而不是误删屏障。没有异步消息时,msg 变成 null,队列进入等待或 IdleHandler 判断。
5. 摘除消息
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:next()
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
if (prevMsg.next == null) {
mLast = prevMsg;
}
} else {
mMessages = msg.next;
if (msg.next == null) {
mLast = null;
}
}
msg.next = null;
msg.markInUse();
if (msg.isAsynchronous()) {
mAsyncMessageCount--;
}
return msg;prevMsg == null 表示摘除真正的队头;非 null 表示摘除屏障之后的异步消息或普通链表节点。摘除后清空 msg.next,避免返回给 Looper 的 Message 继续持有队列链表。异步计数在交付时递减,而不是在 Handler.dispatchMessage() 完成后递减。
6. 退出判断
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:next()、dispose()
if (mQuitting) {
dispose();
return null;
}退出判断位于“没有可立即交付消息”之后。已到期消息会先被交付;当队列清空或安全退出清理已经移除未来消息后,mQuitting 才使 next() 释放 native 对象并返回 null。mPtr == 0 的重启尝试则在函数最前面直接返回 null,不能重新使用已经 dispose 的队列。
7. 空闲回调
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:next()、IdleHandler.queueIdle()
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);只有第一次检查且队列为空,或队头消息尚未到期时,才复制当前 IdleHandler 列表。复制发生在锁内,真正执行放在锁外;因此回调可以安全地向队列发送消息。每个 queueIdle() 返回 false 的 Handler 会被移除,返回 true 的 Handler 可在下一次空闲时继续存在。
8. 回调后重查
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:next()
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null;
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;IdleHandler 运行期间可能投递新消息,所以回调结束后把超时重置为 0,下一轮立即重新扫描,不沿用旧的未来消息超时。异常被记录后该 IdleHandler 的 keep 仍为 false,随后会被移除。
9. 文件事件
next() 的 native 唤醒不只来自 Message 入队。MessageQueue 还可以注册文件描述符监听;native Looper 收到 FD 事件后回调 Java dispatchEvents(),回到 next() 的同一轮检查。因而一次 nativePollOnce() 返回只说明“有事件或超时”,不说明一定有 Message。
10. 测试路径
源码文件: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);
mTestableLooper.moveTimeForward(500);
mTestableLooper.processAllMessages();
inOrder.verify(handler).dispatchMessage(messageC);第一组输入验证未到期消息不会被提前返回;推进测试时间后再处理,才会返回第三条。它没有覆盖 native epoll 唤醒、屏障扫描或 IdleHandler 异常。
11. 队列重置
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:resetForTest()
public void resetForTest() {
synchronized (this) {
if (mQuitting) {
return;
}
mIdleHandlers.clear();
removeAllFdRecords();
removeAllMessagesLocked();
resetSyncBarrierTokens();
nativeWake(mPtr);
}
}测试重置清掉消息、IdleHandler、FD 监听并重置屏障 token,然后唤醒队列。它不能复活已退出队列;mQuitting 为 true 时直接返回。这个分支说明“清空队列”和“恢复可用状态”是两个不同动作。
12. 动手验证
rg -n "Message next\(|nextPollTimeoutMillis|pendingIdleHandlerCount|mQuitting" \
frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
rg -n "testDelayedMessageDoesntSend|testMessageSendsAfterDelay|moveTimeForward" \
frameworks/base/tests/testables/tests/src/android/testing/TestableLooperTest.java阅读时按顺序模拟:空队列、未来队头、到期队头、队头屏障加异步消息、IdleHandler 投递新消息、退出标志。每一步都问三个问题:是否持锁、下一次 timeout 是多少、函数返回 Message、null 还是继续循环。
13. 边界说明
本文只追踪 LegacyMessageQueue.next() 的 Java 控制流。nativePollOnce() 内部的 epoll/eventfd、同步屏障的调用者、IdleHandler 的业务用途和消息发送时间换算分别属于其他专题;不能把“poll 被唤醒”直接等同为“Message 已交付”。
