异步消息与 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 中的一个位,语义是“不受同步屏障约束”:
/*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
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
调用方可以只改变一条消息:
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:
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
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 正在阻塞时,异步消息可能成为第一条可交付工作:
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,但在不同投递点显式设置异步:
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:
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 是异步帧消息的生产者:
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 的公开注释明确提醒:异步消息可以相对于同步消息乱序,但异步消息彼此仍按顺序交付。示例队列:
[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/查询消息显式设置异步:
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. 源码验证
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 兼容问题。
