Skip to content

异步消息与 VSYNC

从 Message 标志追踪 Handler 入队、同步屏障穿透和 Choreographer 的 VSYNC 消息路径。

基于android-17.0.0_r1
Android异步消息VSYNCChoreographer源码阅读

异步消息与 VSYNC ​

本文回答一个具体问题:一条 Android Message 为什么能穿过同步屏障,Handler.createAsync() 与手动 Message.setAsynchronous(true) 到底改变了哪一层,以及 Choreographer 的 VSYNC 消息为什么选择在投递点设置异步标志。

建议先阅读 MessageQueue 同步屏障 和 Handler消息发送。前者说明屏障如何扫描异步消息,后者说明 Handler 如何把消息写入队列。本文限定 Android 17 的 Java Message、Handler、LegacyMessageQueue 和 Choreographer;Combined 队列的内部堆/栈结构不在本文展开。

1. 标志定义 ​

源码文件:frameworks/base/core/java/android/os/Message.java

异步不是“在线程池执行”,也不是“无需等待”。它是 Message.flags 中的一个位,语义是“不受同步屏障约束”:

java
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;

public boolean isAsynchronous() {
    return (flags & FLAG_ASYNCHRONOUS) != 0;
}

public void setAsynchronous(boolean async) {
    if (async) {
        flags |= FLAG_ASYNCHRONOUS;
    } else {
        flags &= ~FLAG_ASYNCHRONOUS;
    }
}

这个标志跟随 Message 实例,而不是跟随 Looper 全局状态。同步消息和异步消息仍在同一个 MessageQueue、同一个 Looper 线程中执行;差异只在队头是屏障时,next() 是否允许扫描并交付它。

2. 屏障读取 ​

源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

java
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
    do {
        prevMsg = msg;
        msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());
}

队头不是屏障时,next() 仍按普通时间顺序读取;队头是屏障时,它跳过屏障后的同步消息,寻找第一条异步消息。找到后,消息仍须满足 now >= msg.when 才能交付,所以异步并不取消时间约束。

异步消息之间仍按 when 顺序交付;它只是相对于同步消息获得了屏障穿透资格,并不获得独立优先级队列。

3. 单条设置 ​

源码文件:frameworks/base/core/java/android/os/Message.java

调用方可以只改变一条消息:

java
Message msg = handler.obtainMessage(MSG_FRAME);
msg.setAsynchronous(true);
handler.sendMessageAtTime(msg, timestamp);

setAsynchronous(false) 也会清除该位。它不检查 Message 是否已经入队,也不唤醒 MessageQueue;真正的入队和 wake 仍由 Handler/MessageQueue 完成。调用者必须在发送前设置,避免把已经交付或正在复用的 Message 当作可变全局状态。

4. Handler传播 ​

源码文件:frameworks/base/core/java/android/os/Handler.java

Handler 的异步策略保存在 mAsynchronous:

java
private boolean enqueueMessage(@NonNull MessageQueue queue,
        @NonNull Message msg, long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

因此一个异步 Handler 会在每次 sendMessage、post 或 Runnable 包装消息入队时设置异步位。该策略不会清除调用者已经设置的异步位;同步 Handler 也不会自动把显式异步消息改回同步。

5. 工厂契约 ​

源码文件:frameworks/base/core/java/android/os/Handler.java

java
public static Handler createAsync(@NonNull Looper looper) {
    if (looper == null) throw new NullPointerException("looper must not be null");
    return new Handler(looper, null, true);
}

public static Handler createAsync(@NonNull Looper looper,
        @NonNull Callback callback) {
    if (looper == null) throw new NullPointerException("looper must not be null");
    if (callback == null) throw new NullPointerException("callback must not be null");
    return new Handler(looper, callback, true);
}

createAsync() 的保证是“该 Handler 发送的消息都按异步策略入队”,不是“消息立即执行”。它仍受同一 Looper 的已在执行回调、消息 when 和其他异步消息顺序影响。源码注释还明确:同一个异步 Handler 的消息彼此有序,但不保证与其他 Handler 的消息保持全局顺序。

6. 入队唤醒 ​

源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

屏障存在且 Looper 正在阻塞时,异步消息可能成为第一条可交付工作:

java
boolean 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;
        }
    }
}

if (needWake) {
    nativeWake(mPtr);
}

mAsyncMessageCount 只帮助判断尾插时是否已有更早异步消息;中间插入还要沿时间顺序检查。异步位因此同时影响两个位置:next() 的穿透扫描,以及 enqueueMessage() 在屏障等待期间的 wake 判断。

7. 帧消息 ​

源码文件:frameworks/base/core/java/android/view/Choreographer.java

Choreographer 使用普通 mHandler,但在不同投递点显式设置异步:

java
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);

没有 VSYNC 的路径也把 MSG_DO_FRAME 标成异步;延迟 callback 调度消息 MSG_DO_SCHEDULE_CALLBACK 同样如此。这样同一个 Handler 可以承载多个消息类型,同时由每条消息决定是否穿透屏障,而不把整个 Handler 变成异步。

8. VSYNC消息 ​

源码文件:frameworks/base/core/java/android/view/Choreographer.java

显示事件回调收到 VSYNC 后,把自身包装成异步 Message:

java
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg,
        timestampNanos / TimeUtils.NANOS_PER_MS);

FrameDisplayEventReceiver 的 run() 随后调用 doFrame()。这里的异步标志不是让 VSYNC 在另一个线程执行;它确保消息可以回到 UI Looper,并在 traversal barrier 存在时被 next() 找到。

9. Traversal关系 ​

源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java

ViewRootImpl 是屏障的所有者,Choreographer 是异步帧消息的生产者:

java
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        postTraversalBarrier();
        mChoreographer.postVsyncCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalCallback);
    }
}

void doTraversal(long frameTimeNanos) {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        removeTraversalBarrier();
        performTraversals(frameTimeNanos);
    }
}

所以“异步 VSYNC 消息穿透屏障”只完成到达 doTraversal() 前的队列步骤;doTraversal() 仍要删除 barrier,随后被阻挡的同步消息才恢复。异步标记不能代替 barrier 的生命周期清理。

10. Handler选择 ​

源码文件:frameworks/base/core/java/android/view/Choreographer.java

Choreographer 选择“普通 Handler + 单条异步 Message”,而不是 Handler.createAsync(),原因可以从调用点观察:同一个 mHandler 处理 MSG_DO_FRAME、MSG_DO_SCHEDULE_VSYNC、延迟 callback 和其他内部任务;只有需要绕过屏障的投递点显式设置异步。

这两种设计的差异如下:

方案标志写入位置影响范围
Message.setAsynchronous(true)单条消息发送前只有该消息穿透屏障
Handler.createAsync()Handler 统一入队函数该 Handler 的所有消息

不能从“VSYNC 使用异步消息”推导“Choreographer 的 Handler 是异步 Handler”。源码字段和构造路径表明它们是两个不同层次的选择。

11. 普通消息顺序 ​

源码文件:frameworks/base/core/java/android/os/Message.java

Message 的公开注释明确提醒:异步消息可以相对于同步消息乱序,但异步消息彼此仍按顺序交付。示例队列:

text
[barrier, sync-A(when=10), async-B(when=20), sync-C(when=30)]

屏障在队头且 B 到期时,B 可以先交付;A 和 C 仍等待屏障移除。屏障移除后,队列恢复按 when 处理剩余同步消息。异步不是“插到队头”,而是改变屏障扫描的可见集合。

12. 框架其他调用 ​

源码文件:frameworks/base/core/java/android/app/ActivityThread.java

ActivityThread 的内部发送辅助方法对特定 dump/查询消息显式设置异步:

java
private void sendMessage(int what, Object obj, int arg1,
        int arg2, boolean async) {
    Message msg = Message.obtain();
    msg.what = what;
    msg.obj = obj;
    msg.arg1 = arg1;
    msg.arg2 = arg2;
    if (async) {
        msg.setAsynchronous(true);
    }
    mH.sendMessage(msg);
}

这说明异步标记不仅服务 VSYNC;它也可用于框架明确声明“不应被同步屏障挡住”的特定操作。但是否异步必须由调用者的顺序契约决定,不能把它当作通用加速开关。

13. 读取测试 ​

源码文件:frameworks/base/core/tests/coretests/src/android/os/MessageQueueTest.java

当前固定源码中的核心队列测试重点覆盖 token reset 和队列清理;异步穿透的主行为由 LegacyMessageQueue.next() 与 Handler 实现共同承担,不能把普通 MessageQueueTest 的 reset 断言说成完整的 VSYNC 测试。

源码文件:frameworks/base/core/tests/coretests/src/android/os/PowerManagerTest.java

PowerManager 测试使用 Handler.createAsync(Looper.getMainLooper()) 作为测试依赖,验证的是调用方能获得异步 Handler,并不证明屏障下的执行时序。测试阅读时必须区分“工厂返回对象”和“消息确实穿透屏障”这两个命题。

14. 源码验证 ​

bash
rg -n "FLAG_ASYNCHRONOUS|isAsynchronous|setAsynchronous" \
  frameworks/base/core/java/android/os/Message.java \
  frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

rg -n "createAsync|mAsynchronous|setAsynchronous" \
  frameworks/base/core/java/android/os/Handler.java

rg -n "setAsynchronous\(true\)|MSG_DO_FRAME|MSG_DO_SCHEDULE_VSYNC|FrameDisplayEventReceiver" \
  frameworks/base/core/java/android/view/Choreographer.java

rg -n "postTraversalBarrier|removeTraversalBarrier|scheduleTraversals|doTraversal" \
  frameworks/base/core/java/android/view/ViewRootImpl.java

复述主线时应能区分:标志写入者是谁、屏障读取者是谁、真正执行者是谁、屏障所有者是谁;并能解释为什么把 mHandler 改成 createAsync() 会改变比 VSYNC 更多的消息。

15. 适用范围 ​

本文覆盖 Android 17 Java Message、Handler、Legacy MessageQueue、ViewRootImpl 和 Choreographer 的异步消息主线。它不声称异步消息拥有独立线程、固定优先级或实时执行保证,也不展开 CombinedMessageQueue 的内部队列算法、输入事件全部路径和下一篇 async 工厂专题之外的 API 兼容问题。