MessageQueue 同步屏障
同步屏障不是“提高某条消息优先级”的独立调度器,而是插入 MessageQueue 链表的一类特殊节点:当它到达队头时,next() 暂停交付后续同步消息,只搜索异步消息。屏障必须由持有 token 的所有者移除,否则同步消息可以长期滞留。
本文以 Android 17 的 LegacyMessageQueue 为机制主线,并追踪 ViewRootImpl 和 Choreographer 的真实消费者。建议先阅读 MessageQueue消息入队、MessageQueue取消息 和 IdleHandler 空闲回调。本文不展开 Combined/CombinedDeli 的并发数据结构,也不把屏障误写成 Choreographer 自己插入的节点。
1. 节点编码
源码文件:
frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.javaframeworks/base/core/java/android/os/Message.java
普通消息在 enqueueMessage() 中必须有 target:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// 普通消息入队。
}同步屏障绕过这个入口,直接创建一个 target == null 的 Message,把 token 放入 arg1:
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;target == null 是节点类型标记,arg1 是配对删除的身份,when 决定屏障在时间有序链表中的位置。屏障没有 Handler 消费者,也不会从 next() 返回给 Looper.loopOnce()。
2. 插入位置
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:postSyncBarrier()、postSyncBarrier(long)
public int postSyncBarrier() {
return onSyncBarrierPosted(postSyncBarrier(SystemClock.uptimeMillis()));
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
if (mLast != null && mLast.when <= when) {
mLast.next = msg;
mLast = msg;
msg.next = null;
return token;
}
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
// 在 prev 与 p 之间接入屏障。
}
}公开入口使用当前 uptimeMillis(),因此已经排在它之前且 when <= 当前时间 的消息仍可先执行;屏障只阻挡它之后遇到的同步消息。私有入口支持任意 when,when == 0 时直接成为队头。
插入屏障不调用 nativeWake()。屏障的作用是减少当前可交付工作,而不是让 Looper 更早处理新工作;如果 Looper 正在等待,原有唤醒条件仍然足够。
3. 穿透扫描
源码文件: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());
}只有屏障位于原始队头时才切换为“搜索异步消息”。扫描会跳过屏障本身和所有同步节点,停在第一条异步消息;若没有异步消息,msg 变为 null,next() 进入无限等待。屏障处于链表中部时,前面的普通消息仍按顺序交付,直到屏障成为队头才生效。
4. 异步摘除
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
if (now >= msg.when) {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
if (prevMsg.next == null) {
mLast = prevMsg;
}
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
if (msg.isAsynchronous()) {
mAsyncMessageCount--;
}
return msg;
}prevMsg != null 表示本次交付的是屏障后面的异步节点。摘除时修改前驱的 next,屏障本身仍留在队头;因此下一次 next() 仍会重新扫描剩余异步消息。屏障不是“一条异步消息通过后自动失效”的一次性门闩。
5. 入队唤醒
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:enqueueMessage()
needWake = mBlocked && p.target == null && msg.isAsynchronous();
if (when >= mLast.when) {
needWake = needWake && mAsyncMessageCount == 0;
} else {
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
}队列被队头屏障卡住且正在 native poll 时,新入队的异步消息可能需要唤醒 Looper。只有它成为最早可交付的异步消息时才 wake;如果前面已有更早的异步消息,原来的等待时间已经足够。mAsyncMessageCount 为尾插提供 O(1) 的“是否已有异步消息”判断,中插则在遍历过程中取消不必要的 wake。
6. token所有权
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
public int postSyncBarrier() {
return onSyncBarrierPosted(postSyncBarrier(SystemClock.uptimeMillis()));
}返回 token 的唯一用途是传回 removeSyncBarrier(token)。所有者必须保存它,并确保所有完成、取消和异常路径都能删除对应节点。多个屏障可以同时存在;删除 token A 不应影响 token B。
mNextBarrierToken 使用递增 int。resetForTest() 清空消息后还会把 token 生成状态复位,以使测试确定;普通运行时不会因为一次屏障删除而复用该 token。
7. 精确移除
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException(
"The specified message queue synchronization barrier token "
+ "has not been posted or has already been removed.");
}搜索条件同时要求 target == null 和 arg1 == token。token 不存在、已经删除或属于错误队列时都会抛出 IllegalStateException;它不是幂等的清理 API。找到后先从链表拆除,再调用 p.recycleUnchecked() 归还 Message 对象池。
8. 移除唤醒
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
final boolean needWake;
if (prev != null) {
prev.next = p.next;
if (prev.next == null) {
mLast = prev;
}
needWake = false;
} else {
mMessages = p.next;
if (mMessages == null) {
mLast = null;
}
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
if (needWake && !mQuitting) {
nativeWake(mPtr);
}中部屏障删除不改变当前队头,所以不 wake。队头屏障删除后有三种结果:
| 新队头 | needWake | 原因 |
|---|---|---|
| 普通消息 | true | 同步消息现在可以交付 |
| 空队列 | true | 结束“只等异步消息”的旧等待状态 |
| 另一个屏障 | false | 队列仍然受屏障阻塞 |
正在退出时不额外 wake,因为退出流程已经使循环处于可结束状态。
9. 遍历所有者
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
真实 UI 主线由 ViewRootImpl 插入屏障:
void scheduleTraversals() {
checkThreadCompat();
if (!mTraversalScheduled) {
mTraversalScheduled = true;
postTraversalBarrier();
mChoreographer.postVsyncCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalCallback);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}源码注释把一个公开 API 顺序保证写得很具体:requestLayout() 或 invalidate() 调度 traversal 后再 Handler.post() 的同步 Runnable,应在 traversal 之后运行;除非该消息被明确标记为异步。屏障保护的是这条顺序,不是笼统地“让绘制拥有最高优先级”。
10. VSYNC穿透
源码文件:frameworks/base/core/java/android/view/Choreographer.java
Choreographer 没有调用 postSyncBarrier()。它负责让帧调度消息能够穿透已有屏障:
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);无 VSYNC 路径中的 MSG_DO_FRAME 同样设置 asynchronous;FrameDisplayEventReceiver.onVsync() 把自己的 Runnable 包装成 Message 后也调用 setAsynchronous(true):
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(
msg, timestampNanos / TimeUtils.NANOS_PER_MS);因此真实关系是:ViewRootImpl 拥有 traversal barrier,Choreographer 产生能穿透它的异步帧消息。
11. 取消与替换
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
unscheduleTraversals() 在取消 VSYNC callback 前先清除 traversal 状态和屏障;doTraversal() 在真正执行 performTraversals() 前也先移除屏障:
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
removeTraversalBarrier();
mChoreographer.removeVsyncCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalCallback);
}
}
void doTraversal(long frameTimeNanos) {
if (mTraversalScheduled) {
mTraversalScheduled = false;
removeTraversalBarrier();
performTraversals(frameTimeNanos);
}
}当前版本还支持原子 traversal barrier 字段:postTraversalBarrier() 用 getAndSet() 替换旧 token 时负责删除被覆盖的屏障;removeTraversalBarrier() 用原子 unset 确保只移除一次。这是并发调度选项下的所有权修复,不能只保存最后一个 token 而遗忘旧节点。
12. 失败后果
未移除的队头屏障不会让整个 Looper 完全停止:异步消息仍可运行,FD callback 和 native wake 也仍存在。但同步 Handler 消息无法越过它,表现可能是普通 Runnable、生命周期或业务回调长期堆积,而动画或 VSYNC 相关异步工作仍有活动。
错误 token 会立刻抛出 IllegalStateException;遗漏 token 则没有自动超时恢复。排查时应同时检查:队头是否 target == null、token 所有者是否走过取消/异常路径、后续是否只有异步消息在交付,以及 dumpsys/bugreport 是否记录待处理屏障 token。
13. 测试证据
源码文件:frameworks/base/core/tests/coretests/src/android/os/MessageQueueTest.java
testResetClearsSyncBarrierTokens 连续创建两个 token,调用 resetForTest() 后再次创建屏障,断言新 token 等于第一次 token。它验证 reset 同时清空屏障消息与 token 状态,不验证同步/异步交付顺序。
源码文件:frameworks/base/core/tests/coretests/src/android/os/MessageStackTest.java
并发 MessageQueue 使用的 MessageStack 测试创建两个 token=123 节点和一个 token=456 节点,调用 moveSyncBarrierToFreelist(123) 后断言只移动一个匹配节点、stack 剩余 2 个、freelist 增加 1 个。它支持“按 token 移除单个屏障”的数据结构语义,但不是 Legacy 链表实现的端到端测试。
源码文件:frameworks/base/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
测试辅助类把 token 封装进 AutoCloseable:构造时 post,close() 时 remove,并在 try-with-resources 中使用。这不证明 MessageQueue 算法,却展示了所有失败/断言路径都必须配对清理的安全模式。
14. 源码复现
rg -n "postSyncBarrier|removeSyncBarrier|target == null|mAsyncMessageCount" \
frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
rg -n "postTraversalBarrier|removeTraversalBarrier|scheduleTraversals|doTraversal" \
frameworks/base/core/java/android/view/ViewRootImpl.java
rg -n "setAsynchronous\(true\)|scheduleVsyncLocked|FrameDisplayEventReceiver" \
frameworks/base/core/java/android/view/Choreographer.java
rg -n "testResetClearsSyncBarrierTokens|moveSyncBarrierToFreelist|class SyncBarrier" \
frameworks/base/core/tests/coretests/src/android/os/MessageQueueTest.java \
frameworks/base/core/tests/coretests/src/android/os/MessageStackTest.java \
frameworks/base/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java阅读时可以画出一条链表:屏障、同步 A、异步 B、同步 C。分别模拟 B 未到期、B 到期、删除错误 token、删除队头屏障后又出现第二个屏障,核对 nextPollTimeoutMillis、needWake 和可交付节点。
15. 适用范围
本文的链表、锁和唤醒结论限定于 Android 17 LegacyMessageQueue;ViewRootImpl/Choreographer 主线来自同一固定源码。Combined/CombinedDeli 实现保留相同公开概念,但内部使用 MessageStack、heap 或 Deli 路径,应单独研究。本文也不展开异步 Handler 的创建方式和 Message flags,它们属于后续异步消息专题。
