同步屏障
“同步屏障”(Synchronization Barrier)是 Android 中 MessageQueue 提供的一种机制,主要用于控制 主线程消息处理的顺序,尤其是为了实现 动画的流畅渲染,在 系统绘制流程(如 Choreographer 动画 vsync 驱动) 中非常关键。
同步屏障是插入到主线程 MessageQueue 中的一个特殊消息节点,它会阻止普通同步消息执行,从而让异步消息(如动画)优先处理。
背景知识:MessageQueue 的两种消息类型
Android 的 MessageQueue 中的消息分为两类:
类型 | 描述 |
---|---|
同步消息 | 普通消息(默认类型),比如点击、网络、UI操作等 |
异步消息 | 特殊消息,不受同步屏障影响,典型如 Vsync 信号驱动的动画 |
同步屏障原理图解
[时间线] --->
MessageQueue:
[同步消息1] → [同步消息2] → [同步屏障] → [异步消息A] → [同步消息3]
Looper.loop() 时:
- 到了同步屏障:同步消息阻塞
- 异步消息A 会被先执行
核心方法(源码层面)
插入屏障:MessageQueue.postSyncBarrier()
int token = MessageQueue.postSyncBarrier();
• 插入一个特殊的同步屏障 Message,标记为 isAsynchronous = false 且 target == null。
移除屏障:MessageQueue.removeSyncBarrier(token)
MessageQueue.removeSyncBarrier(token);
• 通过 token 找到插入的 barrier 并移除,使得之后的同步消息恢复可执行。
在哪里用到了?
主要在ViewRootImpl的scheduleTraversals()方法里面..
效果:
• 避免主线程卡顿(比如点击、布局计算太多影响动画)
• 实现更平滑的 UI 动画(60fps/90fps)
• 提高帧调度优先级
注意事项
• 异步消息必须标记 Message.setAsynchronous(true) 才能穿越屏障。
• 插入了同步屏障后,如果忘记 removeSyncBarrier(),可能会造成消息队列阻塞、ANR。
总结
点 | 说明 |
---|---|
目的 | 保证动画和 Vsync 等异步任务优先处理 |
如何实现 | 向 MessageQueue 插入“同步屏障”,同步消息不能越过,异步消息可以 |
典型用例 | Choreographer、ViewRootImpl 的绘制调度 |
关键 API | postSyncBarrier() / removeSyncBarrier(token) |
异步标记 | Message.setAsynchronous(true) |
下面结合一次完整的 Choreographer + Vsync 消息调度流程来详细讲解:
以 Choreographer 调度动画绘制帧 为例,详细分析 同步屏障(Synchronization Barrier) 的作用机制,配合系统源码来讲清楚这个过程。
背景:为什么需要同步屏障?
Android 主线程有一个 Looper 和 MessageQueue,所有的任务都要排队执行。但动画帧(Vsync 驱动)是有时间约束的,比如 60fps 的话,每帧只有 16.6ms 的时间窗口。如果 MessageQueue 前面堆积了普通消息,动画帧就会被 延迟执行、掉帧、卡顿。
为了优先执行动画帧,Android 设计了 异步消息 + 同步屏障机制。
关键角色
组件 | 功能 |
---|---|
Looper | 轮询主线程消息 |
MessageQueue | 存储主线程待处理的消息 |
Message | 消息本体,可标记为异步(setAsynchronous(true)) |
Choreographer | 控制动画/输入/绘制等帧调度逻辑 |
ViewRootImpl | 应用窗口的根 View 控制类,发起绘制流程 |
Vsync 信号 | 由 DisplayEventReceiver 通知 Choreographer 动画帧开始 |
整体流程图(含同步屏障)
[MessageQueue 初始状态]
| 同步消息1 | 同步消息2 | 同步屏障(B) | 异步Vsync消息 | 同步消息3 |
Looper.loop() 开始轮询:
1️⃣ 遇到同步屏障:前面的同步消息1、2 被跳过;
2️⃣ 找到异步Vsync消息:执行 `Choreographer#doFrame()`
3️⃣ doFrame 中执行动画、布局、绘制任务(包括 measure/layout/draw)
4️⃣ 绘制完毕后,移除同步屏障 B;
5️⃣ 恢复正常消息处理流程
源码详解
总体概览:一帧的执行流程
ViewRootImpl.scheduleTraversals()
↓
[VSYNC 信号]
↓
DisplayEventReceiver.onVsync()
↓
Choreographer.doFrame()
↓
(插入同步屏障,投递异步 Message)
↓
Looper -> MessageQueue -> 异步 Message 执行
↓
Choreographer#doFrame()
↓
doCallbacks(Input → Animation → Traversal)
↓
ViewRootImpl.doTraversal()
↓
performTraversals() → measure/layout/draw
↓
View.draw() → onDraw() → Canvas
一、Vsync 信号产生和监听
1. DisplayEventReceiver 接收 Vsync
系统每 16.6ms 发出一次 VSYNC 信号(如果是 60fps),由底层的 DisplayEventReceiver(native) 捕捉。
// ViewRootImpl.java
public void scheduleTraversals() {
//插入同步屏障(Message.target == null)插入同步屏障后,MessageQueue 中所有普通同步消息都被暂时“屏蔽”
mMessageQueue.postSyncBarrier();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
2. 通知 Choreographer:
// DisplayEventReceiver.java
public void onVsync(long vsyncTimestampNanos, ...) {
// 通知 choreographer,下一帧来了
mChoreographer.scheduleFrameLocked(vsyncTimestampNanos);
}
二、Choreographer 接收 vsync,准备帧回调
// Choreographer.java
void scheduleFrameLocked(long vsyncTimeNanos) {
if (!mFrameScheduled) {
mFrameScheduled = true;
mLastFrameTimeNanos = vsyncTimeNanos;
postCallbackDelayedInternal(); // 插入异步消息
}
}
三、插入同步屏障 + 异步消息
void postCallbackDelayedInternal(...) {
Message msg = Message.obtain(mHandler, mFrameDisplayEventReceiver);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, frameTimeMillis);
}
• 接下来发出的 Vsync 消息由于被标记为 异步,可以“穿过屏障”,被优先执行
四、异步 Message 执行 → doFrame()
当 Looper 执行到 Vsync 异步消息,会触发 doFrame():
void doFrame(long frameTimeNanos) {
mFrameScheduled = false;
// 记录本次帧时间
mLastFrameTimeNanos = frameTimeNanos;
// 依次执行三类回调(都是 CallbackQueue):
doCallbacks(CALLBACK_INPUT, frameTimeNanos);
doCallbacks(CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(CALLBACK_TRAVERSAL, frameTimeNanos);
}
五、doCallbacks → View 绘制启动
最重要的是:
// doCallbacks(CALLBACK_TRAVERSAL)
Runnable callback = mTraversalRunnable;
callback.run(); // 实际是 ViewRootImpl.scheduleTraversals()
这个 mTraversalRunnable 的 run() 内容就是:
@Override
public void run() {
performTraversals(); // View 的测量、布局、绘制
}
六、绘制流程(ViewRootImpl)
ViewRootImpl.performTraversals() {
performMeasure();
performLayout();
performDraw(); // 最后调用 View.draw()
}
最终绘制逻辑会走到 View.draw(Canvas) → onDraw(Canvas),也就是你自定义 View 的绘制代码会在这里执行。
七、移除同步屏障
动画绘制完毕,最后需要 移除同步屏障,否则后续同步消息会被一直挂起,造成 UI 卡死。
void removeFrameCallback() {
mMessageQueue.removeSyncBarrier(token);
}
通常这个是在帧调度完成之后由 Choreographer 主动移除。
MessageQueue 中消息结构图
[MessageQueue]
|【同步消息1】|【同步消息2】|【同步屏障】|【异步vsync消息】|【同步消息3】|
执行流程:
- 遇到同步屏障 → 不执行同步消息
- 执行异步vsync消息(触发 doFrame)
- 移除同步屏障
- 后续同步消息恢复执行
总结关键点
步骤 | 关键操作 | 作用 |
---|---|---|
1 | 接收 Vsync 信号 | 通知新的一帧 |
2 | 插入同步屏障 | 阻止主线程中普通消息抢占 |
3 | 发送异步消息 | 让 Vsync 帧优先执行 |
4 | 执行 doFrame | 开始动画、布局、绘制 |
5 | 绘制视图树 | 完整的 measure → layout → draw |
6 | 移除屏障 | 恢复普通同步消息处理 |
示例场景:动画掉帧优化
假设用户在页面上发起动画,同时有大量 UI 操作(如按钮点击、网络更新等):
• 没有同步屏障:大量普通消息可能挤在前面,动画 doFrame 被延迟
• 有了同步屏障:Vsync 异步消息可以优先穿过 MessageQueue 执行,保证每帧都能及时触发 Choreographer#doFrame(),不掉帧
小结
问题 | 解决方法 |
---|---|
同步消息阻塞主线程动画帧 | 插入同步屏障,临时屏蔽同步消息 |
如何继续执行其他消息 | 异步消息可以穿透,处理完再移除屏障 |
谁在用? | Choreographer、ViewRootImpl |
会导致问题吗? | 忘记移除屏障 → 主线程卡死 |