Skip to content

MessageQueue 同步屏障

从 LegacyMessageQueue 屏障节点追踪异步消息穿透、移除唤醒与 ViewRootImpl 遍历时序。

基于android-17.0.0_r1
AndroidMessageQueue同步屏障ViewRootImplChoreographer源码阅读

MessageQueue 同步屏障 ​

同步屏障不是“提高某条消息优先级”的独立调度器,而是插入 MessageQueue 链表的一类特殊节点:当它到达队头时,next() 暂停交付后续同步消息,只搜索异步消息。屏障必须由持有 token 的所有者移除,否则同步消息可以长期滞留。

本文以 Android 17 的 LegacyMessageQueue 为机制主线,并追踪 ViewRootImpl 和 Choreographer 的真实消费者。建议先阅读 MessageQueue消息入队、MessageQueue取消息 和 IdleHandler 空闲回调。本文不展开 Combined/CombinedDeli 的并发数据结构,也不把屏障误写成 Choreographer 自己插入的节点。

1. 节点编码 ​

源码文件:

  • frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
  • frameworks/base/core/java/android/os/Message.java

普通消息在 enqueueMessage() 中必须有 target:

java
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    // 普通消息入队。
}

同步屏障绕过这个入口,直接创建一个 target == null 的 Message,把 token 放入 arg1:

java
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)

java
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()

java
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

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()

java
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

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

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

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 插入屏障:

java
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()。它负责让帧调度消息能够穿透已有屏障:

java
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):

java
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() 前也先移除屏障:

java
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. 源码复现 ​

bash
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,它们属于后续异步消息专题。