在 Perfetto 里看到 vsync-app、vsync-sf 和 App 的 doFrame,很容易把三条轨道当成同一个时刻。这篇沿一次帧请求看 VSync 怎样从 SurfaceFlinger 的调度进入 App,接着怎样和 RenderThread、Buffer、FrameTimeline 对齐。
示例以 120 Hz 设备为主。相位配置、是否启用硬件 VSync,以及 App 是否请求下一帧都会改变轨道形态;本文先分清每个信号代表什么,再谈延迟。
注:本文以 Android 17(API 37)为当前技术参照,涉及 Android 13 的地方用于说明关键机制的引入;文中代码以 AOSP main 的“签名对齐精简摘录”为主,少量位置使用
...省略非主线逻辑,不能当成 Android 17 发布分支的逐行源码,请以目标分支为准。
本文目录
- 系列文章目录
- 什么是 Vsync
- Android 中 Vsync 的基本工作原理
- 在 Perfetto 中观察 Vsync
- Android App 每一帧是如何基于 Vsync 工作的
- 参考文档
- 关于我 && 博客
系列文章目录
- Android Perfetto 系列目录
- Android Perfetto 系列 1:Perfetto 工具简介
- Android Perfetto 系列 2:Perfetto Trace 抓取
- Android Perfetto 系列 3:熟悉 Perfetto View
- Android Perfetto 系列 4:使用命令行在本地打开超大 Trace
- Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程
- Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战
- Android Perfetto 系列 7:MainThread 和 RenderThread 解读
- Android Perfetto 系列 8:深入理解 Vsync 机制与性能分析
- Android Perfetto 系列 9:CPU 信息解读
- Android Perfetto 系列 10:Binder 调度与锁竞争
- Android Perfetto 系列 11:PerfettoSQL、Trace Processor 与回归检测
- Android Perfetto 系列 12:Trace 数据流与丢失排查
- Android Perfetto 系列 13:Perfetto SDK、Track Event 与 App 现场 Trace
- Android Perfetto 系列 14:heapprofd 与内存 Profiling
- Android Perfetto 系列 15:Boot Trace、长时间现场 Trace 与偶发问题抓取
- Android Perfetto 系列 16:GPU、Power Counters 与硬件瓶颈分析
- Android Perfetto 系列 17:专项场景自动化、问题清单与平台体系
- Android Perfetto 系列 18:响应速度实战,从输入事件到首帧反馈
- 视频(B站) - Android Perfetto 基础和案例分享
- 视频(B站) - Android Perfetto 分享 - 出图类型分享:AOSP、WebView、Flutter + OEM 系统优化分享
如果大家还没看过 Systrace 系列,下面是传送门:
- Systrace 系列目录 : 系统介绍了 Perfetto 的前身 Systrace 的使用,并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。
- 个人博客 :个人博客,主要是 Android 相关的内容,也放了一些生活和工作相关的内容。
欢迎大家在 关于我 页面加入微信群或者星球,讨论你的问题、你最想看到的关于 Perfetto 的部分,以及跟各位群友讨论所有 Android 开发相关的内容
什么是 Vsync
VSync(Vertical Synchronization,垂直同步)给帧生产和显示合成提供共同的时间参考。App、SurfaceFlinger 与显示硬件会按各自的调度相位工作,不能把它们画成同一个瞬间。
在没有 Vsync 机制之前,常见问题是屏幕撕裂(Screen Tearing)。当显示器读取 framebuffer 的同时,GPU 写入了下一帧,就会在同一次刷新中出现上下两部分不一致的画面。
Vsync 解决什么问题?
Android 用 VSync 安排帧回调和合成时点,再用 BufferQueue 与 fence 协调读写:
- 显示节拍:显示硬件提供刷新周期信息,系统也可以根据硬件反馈预测后续时点。
- App 回调:有待处理帧时,Choreographer 请求 VSync,在回调中执行已登记的 Input、Animation、Traversal 等工作;GPU 工作可继续异步运行。
- 合成与同步:SurfaceFlinger 按自己的调度时点取可用 Buffer;acquire fence 约束何时能安全读取,present fence 帮助确认显示完成。
以固定 120 Hz 模式为例,名义显示间隔约 8.33 ms。App 的帧可能在更早的周期开始,也可能错过当前预期呈现时点;是否晚呈现要用 FrameTimeline 和合成侧记录判断,不能只看 queueBuffer 的时间。
Android 中 Vsync 的基本工作原理
Android 系统的 Vsync 实现比基本概念复杂得多,需要考虑多个不同的渲染组件,以及它们之间的协调工作。
Vsync 信号的分层架构
在 Android 系统中,并不是只有一个简单的 Vsync 信号。系统维护着多个不同用途的 Vsync 信号:
硬件 Vsync(HW Vsync):
这是最底层的 Vsync 信号,由显示硬件(HWC,Hardware Composer)产生。它的频率严格对应显示器的刷新率,比如 60Hz 的显示器会每 16.67ms 产生一次 HW Vsync,120Hz 的显示器会每 8.333ms 产生一次。(硬件 Vsync 回调由 HWC/SurfaceFlinger 管理,详见 frameworks/native/services/surfaceflinger 相关实现)
但是,HW Vsync 并不是一直开启的。由于频繁的硬件中断会消耗较多的电量,Android 系统采用了一种智能的策略:只有在需要精确同步的时候才开启 HW Vsync,大部分时间使用软件预测的方式生成 Vsync 信号。
Vsync-app(应用 Vsync):
这是专门用于驱动应用层渲染的 Vsync 信号。当应用需要进行 UI 更新时(比如用户触摸、动画运行、界面滚动等),应用会向系统申请接收 Vsync-app 信号。
1 | // frameworks/base/core/java/android/view/Choreographer.java |
Vsync-app 是按需申请的。如果应用界面是静态的,没有任何动画或用户交互,那么应用不会申请 Vsync-app 信号,系统也就不会为这个应用生成 Vsync 事件。
Vsync-sf(SurfaceFlinger Vsync):
这是专门用于驱动 SurfaceFlinger 进行图层合成的 Vsync 信号。SurfaceFlinger 是 Android 系统中负责将所有应用的图层合成为最终画面的服务。
Vsync-appSf(应用-SurfaceFlinger Vsync):
Android 13 引入的新信号类型。为消除旧设计中 sf EventThread 既唤醒 SurfaceFlinger 又服务部分 Choreographer 客户端带来的时序歧义,系统将两类职责分离:vsync-sf 专注唤醒 SurfaceFlinger,vsync-appSf 面向需要与 SurfaceFlinger 同步的客户端。
在 Perfetto 中观察 Vsync
Perfetto trace 中包含多个与 Vsync 相关的 Track,理解这些 Track 的含义有助于分析性能问题。
在 SurfaceFlinger 进程中:
vsync-app
这份 Trace 中该 counter 在 0 和 1 之间变化,可用变化时点辅助观察调度节奏。它不是某个指定 App 已收到回调的证明。

vsync-sf
显示 SurfaceFlinger Vsync 信号状态。无 Vsync Offset 时与vsync-app同步变化。

vsync-appSf
Android 13+ 新增,服务于需要与 SurfaceFlinger 同步的特殊 Choreographer 客户端。

HW_VSYNC
这条 counter 记录硬件 VSync 的启用状态,值为 1/0 时分别表示开启/关闭。不要把每次状态变化当成一次 App 帧回调。

在应用进程中:
FrameDisplayEventReceiver.onVsync Slice Track:
显示应用接收 Vsync 信号的时间点。该事件连接通过 Binder 建链、通过 BitTube/Looper 通道分发事件,时间可能略晚于 SurfaceFlinger 中的 vsync-app。

UI Thread Slice Track:
包含 Choreographer#doFrame 及相关的 Input、Animation、Traversal 等 Slice。每个 doFrame 对应一帧的处理工作。

RenderThread Slice Track:
包含 DrawFrame、syncAndDrawFrame、queueBuffer 等 Slice,对应渲染线程工作。

Android App 每一帧是如何基于 Vsync 工作的
下图把 App 请求回调、主线程执行和 SurfaceFlinger 合成放在一条时间线上。不同帧可能跳过某些阶段,图中箭头表示调查顺序,不表示所有工作都在一个 VSync 周期内完成。

流程总览(按顺序)
- 触发重绘/输入:
View.invalidate()、动画、数据变化或输入事件触发 →ViewRootImpl.scheduleTraversals()→Choreographer.postCallback(TRAVERSAL) - 申请 Vsync:
Choreographer通过DisplayEventReceiver.scheduleVsync()申请下一次 Vsync(app 相位) - 接收 Vsync:
DisplayEventReceiver.onVsync()收到 Vsync 后,向主线程消息队列投递异步消息 - 主线程帧处理:
Choreographer.doFrame()按顺序执行五类回调:INPUT → ANIMATION → INSETS_ANIMATION → TRAVERSAL → COMMIT - 渲染提交:
RenderThread执行syncAndDrawFrame/DrawFrame,CPU 记录 GPU 命令,queueBuffer提交到 BufferQueue - 合成显示:有待处理更新时,
SurfaceFlinger按vsync-sf调度合成(GPU 或 HWC),生成present_fence,输出到显示 - 帧完成度量:通过
FrameTimeline(PresentType/JankType)与acquire/present_fence判定是否按期显示
下面分别展开每一步的关键实现与 Perfetto 观测点。
App 什么时候会申请 Vsync 信号
应用并不是时刻都在申请 Vsync 信号的。Vsync 信号是按需申请的,只有在以下情况下,应用才会向系统申请下一个 Vsync:
常见的触发申请 Vsync 的场景:
- UI 更新需求:当 View 调用
invalidate()时 - 动画执行:ValueAnimator、ObjectAnimator 等动画开始时
- 用户交互:触摸事件、按键事件等需要 UI 响应时
- 数据变化:RecyclerView 数据更新、TextView 文本改变等
App 申请 Vsync 的完整流程
当应用需要更新 UI 时,会通过以下流程申请 Vsync 信号:
1 | // 1. UI 组件请求重绘 |
TRAVERSAL仍然是最常见触发源,但从 AOSP main 实现看,并非“只有 TRAVERSAL 才申请 Vsync”。
主线程如何监听 Vsync 信号
应用主线程通过 DisplayEventReceiver 来监听 Vsync 信号。这个过程涉及几个关键步骤:
1. 建立连接:
1 | // frameworks/base/core/java/android/view/Choreographer.java |
2. 接收 Vsync 信号:
1 |
|
几个遗留问题
Q1:为什么不在 onVsync() 中直接执行 doFrame()?
- 线程边界:在
Choreographer场景下,onVsync()回调运行在其绑定的 Looper(通常就是主线程);通过消息队列再进入doFrame(),可统一调度并保持帧处理时序一致 - 调度控制:通过
sendMessageAtTime()精确对齐执行时刻 - 队列语义:进入主线程 MessageQueue,确保与其他高优先级任务协同
Q2:Vsync 消息来了但主线程在忙,会丢吗?
- 不完全是“不会丢”。单次
scheduleVsync()只请求一次事件;主线程长期繁忙时会出现“跳过多个硬件节拍、最终只处理较新的一帧”的现象。实际分析应结合 FrameTimeline 判断是否产生可见卡顿。 - AOSP
DisplayEventDispatcher::processPendingEvents明确会用“后到达的 vsync 覆盖先到达的 vsync”(只保留最近一次用于分发)。
Q3:CPU/GPU 是否必须在单个 Vsync 周期内完成?如果任何一个环节超过 1 个 vsync ,都会导致掉帧?
现代 Android 系统采用多缓冲(通常是三缓冲)机制:
应用端:Front Buffer(显示中)+ Back Buffer(渲染中)+ 可能的第三个 Buffer
SurfaceFlinger 端:也有类似的缓冲机制
这也说明即使应用的某一帧超过了 Vsync 周期,也不一定会立即掉帧。
GPU 可以异步执行;多缓冲允许不同帧的生产与消费重叠,也可能带来额外排队延迟。下图里的 Buffer counter 只能说明数量变化,不能单独证明「没有掉帧」。
判断这次是否出现可见卡顿,还要对齐目标 layer 的 FrameTimeline 与呈现时间。

Q4:GPU 和 CPU 是怎么协同的?:
- GPU 渲染是异步的,这带来了额外的复杂性:
- CPU 工作正常,GPU 成为瓶颈:即使应用主线程在 Vsync 周期内完成工作,GPU 渲染耗时过长仍会导致掉帧
- GPU Fence 机制:在 Buffer 被 SF latch 的阶段,关键同步点通常是
acquire fence(Buffer 何时可安全读取);present fence记录该帧显示完成的时点。根据系统Latch Unsignaled Buffers策略,SurfaceFlinger 在特定条件下可先推进流程,再在实际读取 Buffer 前等待 fence 信号。

Q5:VSync 相位会影响什么?
- 改变预算位置:App 与 SurfaceFlinger 的唤醒时点不同,可能让某些帧赶上更早的合成周期,也可能缩短另一侧可用时间。收益不能统一写成从 3 个周期缩到 2 个周期。
- 影响超时边界:相位调整会改变帧截止时间;App 本身过慢时,改相位不能替代减少主线程或 GPU 耗时。
VsyncWorkDuration更接近调度预算(workDuration/readyDuration)的可视化,不等价于单一 appOffset 数值;分析时建议结合vsync-app/sf与 FrameTimeline 联动判断。- 下图中显示的时间段就是我手上的手机配置的 app offset (13.3ms)。不同厂商和机型的配置差异很大,而且在 workDuration 口径下这个值甚至可以超过一个 Vsync 周期,不要拿别的机器的数值直接对比

Vsync Offset / WorkDuration 的技术实现
在当前 AOSP main 中,配置入口是 VsyncConfiguration 抽象接口,返回的是按场景组织的 VsyncConfigSet。实现上 PhaseOffsets 属于旧路径,WorkDuration 是新路径中更常见的实现之一:
1 | // frameworks/native/services/surfaceflinger/Scheduler/VsyncConfiguration.h |
关键概念:
workDuration/readyDuration:调度时的“工作预算”和“就绪提前量”,用于计算回调唤醒时刻app/sf offset:仍可作为常用分析口径,但本质是配置集合与调度模型共同作用的结果- 常用口径里“app/sf offset 差值”指两者相位差(通常看
|sfOffset - appOffset|的绝对值,具体符号以设备实现与统计口径为准)
相位差的简化时序示意
下面把 120 Hz 周期画成 8.333 ms,假设 App 能在 3 ms 完成、SurfaceFlinger 再用 2 ms 合成。数字仅用于说明相位可能让一帧赶上不同的呈现窗口,并非设备实测:
无 Offset(传统方式):
- T0:应用和 SurfaceFlinger 同时接收 Vsync
- T0+3ms:应用完成渲染
- T0+8.333ms:下一个 Vsync,SurfaceFlinger 开始合成
- T0+16.666ms:用户看到画面(总延迟 16.666ms)
有 Offset(优化方式):
- T0+1ms:应用接收 Vsync-app,开始渲染
- T0+3ms:应用完成渲染,提交 Buffer
- T0+4ms:SurfaceFlinger 接收 Vsync-sf,立即开始合成
- T0+6ms:SurfaceFlinger 完成合成
- T0+8.333ms:用户看到画面(总延迟 8.333ms)
在这个理想化例子里,改变相位让帧赶上了前一个显示机会。真实设备还要算输入到达、GPU、Buffer 排队和 present fence;不能由这张示意图推出「响应性能提升一倍」。
实际的时间预算分配:
以 120Hz 设备为例(8.333ms 周期):
- 示意预算:若 App 的预期完成窗口为 4 ms、合成使用 2 ms,剩余时间仍受两者调度相位约束,不能简单相加成统一截止线。
- 跨周期执行:App、GPU 与 SurfaceFlinger 可以处理不同帧;多缓冲允许重叠,但不能保证按时呈现。
- GPU 限制:GPU 若持续晚完成,acquire fence 会延后可安全读取的时间;用实际 GPU 轨道或 fence 数据核对。
出现晚帧时可继续排查:
- 应用端超时 + Buffer 耗尽:连续多帧超时导致 BufferQueue 没有可用 Buffer
- GPU 渲染超时:即使 CPU 工作正常,GPU 渲染超时也会掉帧
- SurfaceFlinger 超时:系统级合成超时,影响所有应用
- 系统资源竞争:CPU/GPU/内存等资源被其他进程占用
Vsync 信号的完整代码流程
Vsync 信号从硬件传递到应用层的完整链路如下。
按 AOSP main 分支对齐的关键代码(精简摘录)
下面片段都按当前 AOSP main 分支的方法签名整理,省略了与主线无关的分支与日志代码。
1)Choreographer 申请下一次 Vsync(Java)
1 | // frameworks/base/core/java/android/view/Choreographer.java |
2)Choreographer 接收 Vsync(Java)
1 | // frameworks/base/core/java/android/view/Choreographer.java |
3)JNI 层桥接:DisplayEventDispatcher(C++)
1 | // frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp |
4)Native 收发通道:DisplayEventReceiver + BitTube(C++)
1 | // frameworks/native/libs/gui/DisplayEventReceiver.cpp |
5)SurfaceFlinger 调度与分发(C++)
1 | // frameworks/native/services/surfaceflinger/Scheduler/VSyncDispatch.h |
1 | // frameworks/native/services/surfaceflinger/Scheduler/EventThread.cpp |
关键时序点分析
沿着上述代码流程,可以梳理出完整的时序链路:
- HWC 硬件 Vsync 或软件预测节拍 → SurfaceFlinger Scheduler 更新显示时序
- Scheduler 计算唤醒窗口 →
VSyncDispatch::schedule(...) - EventThread 生成/派发事件 → 写入
DisplayEventReceiver::Event(通过BitTube) - App 侧 Native 收到事件 →
DisplayEventDispatcher::dispatchVsync(...) - Java
FrameDisplayEventReceiver回调 → 异步消息切到 Looper 队列 Choreographer#doFrame(...)执行 → Input/Animation/Traversal/Commit
各环节的职责和优化点不同,理解完整流程有助于在 Perfetto 中分析 Vsync 相关性能问题。
FrameTimeline
App 和 SurfaceFlinger 都有 FrameTimeline

- 轨道:
Expected Timeline、Actual Timeline - PresentType/JankType:
- PresentType 指示本帧呈现方式(例如 On-time、Late),JankType 指示卡顿类型来源
- 常见 JankType:
AppDeadlineMissed、BufferStuffing、SfCpuDeadlineMissed、SfGpuDeadlineMissed等 - 操作步骤(Perfetto UI):
- 在应用进程选择目标
Surface/Layer或使用 FrameToken 过滤 - 对齐 Expected 与 Actual,查看偏移与颜色编码
- 向上钻取:
Choreographer#doFrame、RenderThread、queueBuffer、acquire/present_fence
- 误判规避:
- 仅凭
doFrame时长判断掉帧不可靠;以 FrameTimeline 的 PresentType/JankType 为准 - 多缓冲可能掩盖单帧超时,需要看连续帧与 Buffer 可用性
刷新率/显示模式/VRR 对 Vsync 与 Offset/预测的影响
- 模式切换:刷新率变更会重新配置
VsyncConfiguration,影响 app/sf Offset 与预测模型; - Perfetto:查
display mode change事件与随后的vsync间隔变化 - VRR(可变刷新率):目标周期不恒定,软件预测更依赖 present_fence 反馈校准;
- Perfetto:观察
vsync间隔分布与present_fence偏差 - 多显示/外接显示:硬件层可按
physicalDisplayId上报 vsync;但应用侧 Choreographer 通常仍以内屏/pacesetter 时序为主(实现细节随版本演进)。分析时先确认你看的到底是 HWC/SF 轨道,还是 app 轨道; - Android 12–17 分析时,仍要区分框架/HWC 的 per-display VSYNC 能力与应用侧
Choreographer的请求路径;后者可能受 internal display / pacesetter 实现约束,以目标系统分支和设备 Trace 为准 - Perfetto:按显示 ID 过滤相关 Counter/Slice
Perfetto 实战 Checklist(建议按序查看)
- Vsync 信号与周期
vsync-app / vsync-sf / vsync-appSf间隔是否稳定(60/90/120Hz 对应周期)- 是否存在异常密集/稀疏的 Vsync(预测抖动)

- Vsync 相位差配置
VsyncWorkDuration是否符合机型预期的 app/sf Offset- app 与 sf 的先后是否匹配“先绘制后合成”的策略

- FrameTimeline 判读
- 先看
PresentType,再看JankType;确认是 app 还是 SF/GPU 侧问题 - 选择目标 Surface/FrameToken 定位具体帧

- 应用主线程与渲染线程
Choreographer#doFrame各阶段耗时(Input/Animation/Traversal)RenderThread的syncAndDrawFrame/DrawFrame耗时是否异常

- BufferQueue 与 Fence
- 生产者:RenderThread
queueBuffer之后,Buffer 进入可消费队列;但 SF 是否能立刻 latch 还要看acquire fence。present fence主要用于确认该帧实际送显完成时间。新版本在特定策略下可对 unsignaled buffer 先推进,再在需要时等待 fence。

- 消费者 SF 与 BufferTX:SF 在有待处理更新的合成节拍会尝试为目标 layer 取最新可用 Buffer。若某 layer 的 BufferTX 为 0,通常表示该 layer 暂无待处理 Buffer;静态页面属正常情况,只有本应持续更新却没有新 Buffer 时才需排查 App 侧。它不代表 SF 全局“停止合成”。

- 合成策略与显示
- SF 是否频繁走 ClientComposition;HWC validate/present 是否异常
- 多显示/模式切换/VRR 时是否伴随明显预测偏差

- 资源与其他干扰
- CPU 竞争(大核占用)、GPU 忙、IO/内存抖动(GC/compaction)
- 其他前台应用/系统服务是否占用关键资源

参考文档
- Android Graphics Architecture
- VSYNC Implementation Guide
- Frame Pacing
- Perfetto Documentation
- Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程
- Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战
- Vsync offset 相关技术分析
- Android 13/14高版本SurfaceFlinger出现VSYNC-app/VSYNC-appSf/VSYNC-sf剖析
- AOSP - Choreographer.java(main)
- AOSP - android_view_DisplayEventReceiver.cpp(main)
- AOSP - DisplayEventDispatcher.h(main)
- AOSP - DisplayEventReceiver.cpp(main)
- AOSP - VSyncDispatch.h(main)
- AOSP - EventThread.cpp(main)
- Android Multi-display(官方文档)
- AOSP - VsyncConfiguration.h(main)
- AOSP - DisplayEventDispatcher.cpp(main)
- Unsignaled buffer latch(官方文档)
关于我 && 博客
下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
- 博主个人介绍 :里面有个人的微信和微信群链接。
- 本博客内容导航 :个人博客内容的一个导航。
- 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
- Android性能优化知识星球 : 欢迎加入,多谢支持~
一个人可以走的更快 , 一群人可以走的更远
