同步屏障

“同步屏障”(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
会导致问题吗? 忘记移除屏障 → 主线程卡死

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器