惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
NISL@THU
NISL@THU
The Hacker News
The Hacker News
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Tenable Blog
Forbes - Security
Forbes - Security
V2EX - 技术
V2EX - 技术
Webroot Blog
Webroot Blog
Schneier on Security
Schneier on Security
T
The Exploit Database - CXSecurity.com
T
Tor Project blog
C
Cisco Blogs
TaoSecurity Blog
TaoSecurity Blog
The Last Watchdog
The Last Watchdog
PCI Perspectives
PCI Perspectives
O
OpenAI News
C
Cyber Attacks, Cyber Crime and Cyber Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
宝玉的分享
宝玉的分享
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
量子位
D
Docker
AI
AI
Blog — PlanetScale
Blog — PlanetScale
S
Security @ Cisco Blogs
S
Schneier on Security
The GitHub Blog
The GitHub Blog
W
WeLiveSecurity
云风的 BLOG
云风的 BLOG
M
MIT News - Artificial intelligence
P
Privacy International News Feed
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Hackread – Cybersecurity News, Data Breaches, AI and More
B
Blog
C
Check Point Blog
A
About on SuperTechFans
D
Darknet – Hacking Tools, Hacker News & Cyber Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Engineering at Meta
Engineering at Meta
I
InfoQ
T
Threat Research - Cisco Blogs
Project Zero
Project Zero
Cloudbric
Cloudbric
MongoDB | Blog
MongoDB | Blog
Cisco Talos Blog
Cisco Talos Blog
L
Lohrmann on Cybersecurity
S
Securelist

Android Performance

SmartPerfetto 架构文章 Q&A:8 个深度技术问答 从 Trace 到洞察:SmartPerfetto AI Agent 的 Harness Engineering 实战 OpenClaw 常见问题解答:Token 消耗、能干什么、本地模型、隐私安全、使用体验 我把 OpenClaw 跑在本地三周后,发现它根本不是聊天机器人 Android Perfetto 系列 10 - Binder 调度与锁竞争 Android Perfetto 系列 9 - CPU 信息解读 Android Perfetto 系列 7 - MainThread 和 RenderThread 解读 Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战 Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程 Android Perfetto 系列 4:使用命令行在本地打开超大 Trace Android ANR 系列 3 :ANR 案例分享 Android ANR 系列 2 :ANR 分析套路和关键 Log 介绍 Android ANR 系列 1 :理解 Android ANR 设计思想 Android Perfetto 系列 3:熟悉 Perfetto View Android Perfetto 系列 2:Perfetto Trace 抓取 Android Perfetto 系列 1:Perfetto 工具简介 Android Perfetto 系列目录 2023 年的方方面面 关于 The Android Performance 知识星球介绍 The Performance Design Of OS OS 设计之性能设计 当 App 有了系统权限,真的可以为所欲为? The Performance 星球茶话会 - 第一期 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Android 性能优化的术、道、器 Techniques, Philosophy, and Tools for Android Performance Optimization 回顾 2021 一本讲 Android 流畅性的书,应该有什么内容? Android 系统开发系列(1):Android 12 源代码下载、编译和刷机 Android Systrace 响应速度实战 3 :响应速度延伸知识 Android Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Android Systrace 响应速度实战 1 :了解响应速度原理 Android Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 Android Systrace 流畅性实战 2 :案例分析 - MIUI 桌面滑动卡顿分析 Android Systrace 流畅性实战 1 :了解卡顿原理 华为手机刷微博体验更好?技术角度的一些分析和思考 一个「闰」字引发的事故 - 三星系统重启分析 Android App 链式唤醒分析 Android Systrace 基础知识 - SurfaceFlinger 解读 Android 开发者学习路线(2020 版本) 我的 2020 年读书单 Android Systrace 基础知识 - CPU Info 解读 Android Systrace 基础知识 - Triple Buffer 解读 Android Systrace 基础知识 - Binder 和锁竞争解读 「置顶」博客文章目录 Android Systrace 基础知识 - Vsync 解读 Android App 启动优化全记录 Android Systrace 基础知识 - MainThread 和 RenderThread 解读 Android Systrace 基础知识 - Input 解读 Android 中的“后台无效动画“行为分析 Android 基于 Choreographer 的渲染机制详解 Android 中的卡顿丢帧原因概述 - 低内存篇 Android 桌面被杀问题分析案例 Android 中的卡顿丢帧原因概述 - 应用篇 Android 中的卡顿丢帧原因概述 - 系统篇 Android 中的卡顿丢帧原因概述 - 方法论 Android 中的 Activity Launch Mode 详解 Android 中的 Hardware Layer 详解 Android Systrace 基础知识 -- 分析 Systrace 预备知识 Android Systrace 基础知识 - SystemServer 解读 Android Systrace 基础知识 -- Systrace 简介 Android Systrace 基础知识 -- Why 60 fps ? Android Systrace -- 系列文章目录 Android 新的流畅体验,90Hz 漫谈 利器 - 高效工具推荐 Android 无障碍服务导致的整机卡顿案例分析 2018 年度好物推荐 - 给辛勤工作的自己一点奖励 Android 系统开发源码环境搭建 陆奇:除了好代码,工程师怎样才算优秀? 程序员的修炼-08-阅读之美 程序员的修炼-07-游戏与编程 程序员的修炼-06-互联网那些事 程序员的修炼-05-了解你的用户 程序员的修炼-04-关于测试的一些思考 程序员的修炼-03-Web 设计原则 程序员的修炼-02-编程之道 程序员的修炼-01-绝地反击之术 Android 系统不释放内存吗? 关于 Android 系统流畅性的一些思考 知乎 救救你的 StartingWindow 「置顶」Android 性能优化必知必会 2017 年度好物推荐 - 给辛勤工作的自己一点奖励 2017 Android Bottom navigation 规范二:样式、行为与规格 Android Bottom Navigation 规范一:使用方法 Android 中如何计算 App 的启动时间? Android 应用启动优化:一种 DelayLoad 的实现和原理(下篇) Android 应用启动优化 - 一种 DelayLoad 的实现和原理(上篇) Android hwui 中 RenderThread 工作流程 Java7 HashMap 源码分析 Android 代码内存优化建议 - OnTrimMemory 优化 Android 代码内存优化建议 - Android 资源篇 Android 代码内存优化建议 - Android 官方篇 Android 代码内存优化建议 - Java 官方篇 Nexus6 with Android M 开启多窗口模式 细说 Java 单例模式 Android 性能优化典范 - Profile GPU Rendering Android 性能优化典范 - Understanding VSYNC
Android Perfetto 系列 8:深入理解 Vsync 机制与性能分析
Gracker · 2025-08-05 · via Android Performance

本篇是 Perfetto 系列文章的第八篇,主要深入介绍 Android 中的 Vsync 机制及其在 Perfetto 中的表现形式。文章将从 Perfetto 的角度来分析 Android 系统如何基于 Vsync 信号进行帧渲染和合成,涵盖 Vsync、Vsync-app、Vsync-sf、VsyncWorkDuration 等核心概念。

随着高刷新率屏幕的普及,理解 Vsync 机制变得更加重要。本文将以 120Hz 刷新率为主要叙事线,帮助开发者理解现代 Android 设备中 Vsync 的工作原理,以及如何在 Perfetto 中观察和分析 Vsync 相关的性能问题。

注:本文内容基于 Android 13~16 的公开实现与演进;文中代码以 AOSP main 的“签名对齐精简摘录”为主,少量位置使用 ... 省略非主线逻辑,请以当前分支源码为准。

本文目录

  • 系列文章目录
  • 什么是 Vsync
  • Android 中 Vsync 的基本工作原理
  • 在 Perfetto 中观察 Vsync
  • Android App 每一帧是如何基于 Vsync 工作的
  • 参考文档
  • 关于我 && 博客

系列文章目录

  1. Android Perfetto 系列目录
  2. Android Perfetto 系列 1:Perfetto 工具简介
  3. Android Perfetto 系列 2:Perfetto Trace 抓取
  4. Android Perfetto 系列 3:熟悉 Perfetto View
  5. Android Perfetto 系列 4:使用命令行在本地打开超大 Trace
  6. Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程
  7. Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战
  8. Android Perfetto 系列 7 - MainThread 和 RenderThread 解读
  9. Android Perfetto 系列 8:深入理解 Vsync 机制与性能分析
  10. Android Perfetto 系列 9 - CPU 信息解读
  11. Android Perfetto 系列 10 - Binder 调度与锁竞争
  12. 视频(B站) - Android Perfetto 基础和案例分享
  13. 视频(B站) - Android Perfetto 分享 - 出图类型分享:AOSP、WebView、Flutter + OEM 系统优化分享

如果大家还没看过 Systrace 系列,下面是传送门:

  1. Systrace 系列目录 : 系统介绍了 Perfetto 的前身 Systrace 的使用,并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。
  2. 个人博客 :个人博客,主要是 Android 相关的内容,也放了一些生活和工作相关的内容。

欢迎大家在 关于我 页面加入微信群或者星球,讨论你的问题、你最想看到的关于 Perfetto 的部分,以及跟各位群友讨论所有 Android 开发相关的内容

什么是 Vsync

Vsync(Vertical Synchronization,垂直同步)是 Android 图形系统的核心机制,它的存在是为了解决一个根本性的问题:如何让软件的渲染节奏与硬件的显示节奏保持同步。

在没有 Vsync 机制之前,常见问题是屏幕撕裂(Screen Tearing)。当显示器读取 framebuffer 的同时,GPU 写入了下一帧,就会在同一次刷新中出现上下两部分不一致的画面。

Vsync 解决什么问题?

Vsync 机制的核心思想非常简单:让所有的渲染工作都按照显示器的刷新节拍来进行。具体来说:

  1. 同步信号:显示器每次开始新的刷新周期时,都会发出一个 Vsync 信号。
  2. 帧节拍与生产:应用侧在 Vsync 到来时由 Choreographer 驱动开始一帧的生产(Input/Animation/Traversal);CPU 提交渲染命令后,GPU 异步流水执行。SurfaceFlinger 侧在 Vsync 到来时进行 Buffer 的合成操作。
  3. 缓冲机制:使用双缓冲或三缓冲技术,确保显示器总是读取完整的帧数据。

这样,帧的生产与显示以 Vsync 为节拍对齐。以 120Hz 为例,每 8.333ms 会有一个显示机会;应用需要在该窗口前把可合成的 Buffer 提交给 SurfaceFlinger。关键约束是 queueBuffer/acquire_fence/present_fence 的时序;若未赶上本周期,会顺延到下一个周期显示。

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
2
3
4
5
6
7
8
9
10

private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {

mDisplayEventReceiver.scheduleVsync();
}
}
}

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 进程中

  1. vsync-app
    显示应用 Vsync 信号状态,数值在 0 和 1 之间变化。每次数值变化代表一个 Vsync 信号。
    image-20250811221826847

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

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

  4. HW_VSYNC
    显示硬件 Vsync 开启状态。值为 1 表示开启,值为 0 表示关闭。为节省电量,硬件 Vsync 仅在需要精确同步时开启。
    image-20250811222159253

在应用进程中

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

image-20250918220632473

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

image-20250918220709655

RenderThread Slice Track:
包含 DrawFramesyncAndDrawFramequeueBuffer 等 Slice,对应渲染线程工作。

image-20250918220730872

Android App 每一帧是如何基于 Vsync 工作的

Android 应用的每一帧基于 Vsync 机制完成从渲染到显示的完整过程涉及多个关键步骤。

image-20250918221821265

流程总览(按顺序)

  1. 触发重绘/输入:View.invalidate()、动画、数据变化或输入事件触发 → ViewRootImpl.scheduleTraversals()Choreographer.postCallback(TRAVERSAL)
  2. 申请 Vsync:Choreographer 通过 DisplayEventReceiver.scheduleVsync() 申请下一次 Vsync(app 相位)
  3. 接收 Vsync:DisplayEventReceiver.onVsync() 收到 Vsync 后,向主线程消息队列投递异步消息
  4. 主线程帧处理:Choreographer.doFrame() 按顺序执行五类回调:INPUT → ANIMATION → INSETS_ANIMATION → TRAVERSAL → COMMIT
  5. 渲染提交:RenderThread 执行 syncAndDrawFrame/DrawFrame,CPU 记录 GPU 命令,queueBuffer 提交到 BufferQueue
  6. 合成显示:SurfaceFlingervsync-sf 到来时合成(GPU/或HWC),生成 present_fence,输出到显示
  7. 帧完成度量:通过 FrameTimeline(PresentType/JankType)与 acquire/present_fence 判定是否按期显示

下面分别展开每一步的关键实现与 Perfetto 观测点。

App 什么时候会申请 Vsync 信号

应用并不是时刻都在申请 Vsync 信号的。Vsync 信号是按需申请的,只有在以下情况下,应用才会向系统申请下一个 Vsync:

触发申请 Vsync 的场景

  1. UI 更新需求:当 View 调用 invalidate()
  2. 动画执行:ValueAnimator、ObjectAnimator 等动画开始时
  3. 用户交互:触摸事件、按键事件等需要 UI 响应时
  4. 数据变化:RecyclerView 数据更新、TextView 文本改变等

App 申请 Vsync 的完整流程

当应用需要更新 UI 时,会通过以下流程申请 Vsync 信号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49


public void invalidate() {

mPrivateFlags |= PFLAG_DIRTY;

if (mParent != null && mAttachInfo != null) {

mParent.invalidateChild(this, null);
}
}



void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;

mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}



private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);


if (dueTime <= now) {
scheduleFrameLocked(now);
}
}

private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
}
}

TRAVERSAL 仍然是最常见触发源,但从 AOSP main 实现看,并非“只有 TRAVERSAL 才申请 Vsync”。

主线程如何监听 Vsync 信号

应用主线程通过 DisplayEventReceiver 来监听 Vsync 信号。这个过程涉及几个关键步骤:

1. 建立连接

1
2
3
4
5
6
7
8
9

private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {

public FrameDisplayEventReceiver(Looper looper, int vsyncSource, long layerHandle) {
super(looper, vsyncSource, 0, layerHandle);

}
}

2. 接收 Vsync 信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {

mTimestampNanos = timestampNanos;
mFrame = frame;
mLastVsyncEventData.copyFrom(vsyncEventData);


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

@Override
public void run() {

doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}

几个遗留问题

Q1:为什么不在 onVsync() 中直接执行 doFrame()
image-20250918221936675

  • 线程边界:在 Choreographer 场景下,onVsync() 回调运行在其绑定的 Looper(通常就是主线程);通过消息队列再进入 doFrame(),可统一调度并保持帧处理时序一致
  • 调度控制:通过 sendMessageAtTime() 精确对齐执行时刻
  • 队列语义:进入主线程 MessageQueue,确保与其他高优先级任务协同

Q2:Vsync 消息来了但主线程在忙,会丢吗?
image-20250918222045731

  • 不完全是“不会丢”。单次 scheduleVsync() 只请求一次事件;主线程长期繁忙时会出现“跳过多个硬件节拍、最终只处理较新的一帧”的现象。实际分析应结合 FrameTimeline 判断是否产生可见卡顿。
  • AOSP DisplayEventDispatcher::processPendingEvents 明确会用“后到达的 vsync 覆盖先到达的 vsync”(只保留最近一次用于分发)。

Q3:CPU/GPU 是否必须在单个 Vsync 周期内完成?如果任何一个环节超过 1 个 vsync ,都会导致掉帧?

  • 现代 Android 系统采用多缓冲(通常是三缓冲)机制:

  • 应用端:Front Buffer(显示中)+ Back Buffer(渲染中)+ 可能的第三个 Buffer

  • SurfaceFlinger 端:也有类似的缓冲机制

  • 这也说明即使应用的某一帧超过了 Vsync 周期,也不一定会立即掉帧。

  • GPU 异步流水;关键是 queueBuffer 是否赶上 SF 合成窗口,多缓冲可掩盖单帧延迟但可能引入额外时延。下图里可以直接看到,App 端的 BufferQueue 和 SurfaceFlinger 端的 Buffer 都是充足的,且有冗余,所以没有掉帧。

  • 但是如果 App 在之前没有堆积 Buffer ,则还是会出现掉帧。

image-20250918222258536

Q5:GPU 和 CPU 是怎么协同的?

  • GPU 渲染是异步的,这带来了额外的复杂性:
  • CPU 工作正常,GPU 成为瓶颈:即使应用主线程在 Vsync 周期内完成工作,GPU 渲染耗时过长仍会导致掉帧
  • GPU Fence 机制:在 Buffer 被 SF latch 的阶段,关键同步点通常是 acquire fence(Buffer 何时可安全读取);present fence 更偏向“该帧何时真正送显”的完成信号。根据系统 Latch Unsignaled Buffers 策略,SurfaceFlinger 在特定条件下可先推进流程,再在真正需要时等待 fence 信号,以此隐藏部分延迟。

image-20250918222626100

Q6:Vsync Phase(相位差)的真正作用是什么?

  • 提升跟手性:通过调整 sf vsync 的相位差,可以让应用从开始绘制到显示在屏幕上的时间从 3 个 Vsync 周期缩短到 2 个 Vsync 周期。这对于触摸响应等交互场景非常重要。
  • 解决应用绘制超时问题:当应用绘制超时时,合理的 sf 相位差可以为应用争取更多的处理时间,避免因为时序不当导致的掉帧。
  • VsyncWorkDuration 更接近调度预算(workDuration/readyDuration)的可视化,不等价于单一 appOffset 数值;分析时建议结合 vsync-app/sf 与 FrameTimeline 联动判断。
  • 下图中显示的时间段就是我手上的手机配置的 app offset (13.3ms)

image-20250918222707300

Vsync Offset / WorkDuration 的技术实现

在当前 AOSP main 中,配置入口是 VsyncConfiguration 抽象接口,返回的是按场景组织的 VsyncConfigSet。实现上 PhaseOffsets 属于旧路径,WorkDuration 是新路径中更常见的实现之一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

class VsyncConfiguration {
public:
virtual ~VsyncConfiguration() = default;
virtual VsyncConfigSet getCurrentConfigs() const = 0;
virtual VsyncConfigSet getConfigsForRefreshRate(Fps fps) const = 0;
virtual void setRefreshRateFps(Fps fps) = 0;
virtual void reset() = 0;
};

class WorkDuration : public VsyncConfiguration {
public:
explicit WorkDuration(Fps currentRefreshRate);

};

关键概念

  • workDuration/readyDuration:调度时的“工作预算”和“就绪提前量”,用于计算回调唤醒时刻
  • app/sf offset:仍可作为常用分析口径,但本质是配置集合与调度模型共同作用的结果
  • 常用口径里“app/sf offset 差值”指两者相位差(通常看 |sfOffset - appOffset| 的绝对值,具体符号以设备实现与统计口径为准)

实际的优化效果

以 120Hz 设备为例,配置 3ms Offset 的效果:

无 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)

通过合理配置 Offset,可以将延迟从 16.666ms 减少到 8.333ms,提升一倍的响应性能。

实际的时间预算分配

以 120Hz 设备为例(8.333ms 周期):

  • 理想情况:应用 4ms + SurfaceFlinger 2ms + 缓冲 2.333ms
  • 但实际可以接受:应用 6ms + SurfaceFlinger 3ms(如果有足够的 Buffer 缓冲)
  • GPU 限制:在低端设备上,GPU 渲染可能需要 10-15ms,成为真正的瓶颈

掉帧的真正原因

  1. 应用端超时 + Buffer 耗尽:连续多帧超时导致 BufferQueue 没有可用 Buffer
  2. GPU 渲染超时:即使 CPU 工作正常,GPU 渲染超时也会掉帧
  3. SurfaceFlinger 超时:系统级合成超时,影响所有应用
  4. 系统资源竞争:CPU/GPU/内存等资源被其他进程占用

Vsync 信号的完整代码流程

Vsync 信号从硬件传递到应用层的完整链路如下。

按 AOSP main 分支对齐的关键代码(精简摘录)

下面片段都按当前 AOSP main 分支的方法签名整理,省略了与主线无关的分支与日志代码。

1)Choreographer 申请下一次 Vsync(Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
}
}
}

private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}

2)Choreographer 接收 Vsync(Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19



@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {
mTimestampNanos = timestampNanos;
mFrame = frame;
mLastVsyncEventData.copyFrom(vsyncEventData);
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}

@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}

3)JNI 层桥接:DisplayEventDispatcher(C++)

1
2
3
4
5
6
7
8
9
10
11
12

class NativeDisplayEventReceiver : public DisplayEventDispatcher {
public:
NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak, jobject vsyncEventDataWeak,
const sp<MessageQueue>& messageQueue, jint vsyncSource,
jint eventRegistration, jlong layerHandle);

private:
void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
VsyncEventData vsyncEventData) override;
void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
};

4)Native 收发通道:DisplayEventReceiver + BitTube(C++)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

DisplayEventReceiver::DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vsyncSource,
EventRegistrationFlags eventRegistration, const sp<IBinder>& layerHandle) {
sf->createDisplayEventConnection(vsyncSource, ..., layerHandle, &mEventConnection);
mDataChannel = std::make_unique<gui::BitTube>();
mEventConnection->stealReceiveChannel(mDataChannel.get());
}

status_t DisplayEventReceiver::requestNextVsync() {
mEventConnection->requestNextVsync();
return NO_ERROR;
}

ssize_t DisplayEventReceiver::getEvents(Event* events, size_t count) {
return gui::BitTube::recvObjects(mDataChannel.get(), events, count);
}

5)SurfaceFlinger 调度与分发(C++)

1
2
3

virtual std::optional<ScheduleResult> schedule(
CallbackToken token, ScheduleTiming scheduleTiming) = 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

void EventThread::onVsync(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) {
mPendingEvents.push_back(makeVSync(mVsyncSchedule->getPhysicalDisplayId(), wakeupTime,
++mVSyncState->count, vsyncTime, readyTime));
}

void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
...
const auto scheduleResult = mVsyncRegistration.schedule({
.workDuration = mWorkDuration.get().count(),
.readyDuration = mReadyDuration.count(),
.lastVsync = mLastVsyncCallbackTime.ns(),
.committedVsyncOpt = mLastCommittedVsyncTime.ns()});
LOG_ALWAYS_FATAL_IF(!scheduleResult, "Error scheduling callback");
...
}

关键时序点分析

沿着上述代码流程,可以梳理出完整的时序链路:

  1. HWC 产生硬件 Vsync → SurfaceFlinger Scheduler 获取硬件节拍
  2. Scheduler 计算唤醒窗口VSyncDispatch::schedule(...)
  3. EventThread 生成/派发事件 → 写入 DisplayEventReceiver::Event(通过 BitTube
  4. App 侧 Native 收到事件DisplayEventDispatcher::dispatchVsync(...)
  5. Java FrameDisplayEventReceiver 回调 → 异步消息切到 Looper 队列
  6. Choreographer#doFrame(...) 执行 → Input/Animation/Traversal/Commit

各环节的职责和优化点不同,理解完整流程有助于在 Perfetto 中分析 Vsync 相关性能问题。

FrameTimeline

App 和 SurfaceFlinger 都有 FrameTimeline

image-20250918223035361

  • 轨道Expected TimelineActual Timeline
  • PresentType/JankType
  • PresentType 指示本帧呈现方式(例如 On-time、Late),JankType 指示卡顿类型来源
  • 常见 JankType:AppDeadlineMissedBufferStuffingSfCpuDeadlineMissedSfGpuDeadlineMissed
  • 操作步骤(Perfetto UI)
  1. 在应用进程选择目标 Surface/Layer 或使用 FrameToken 过滤
  2. 对齐 Expected 与 Actual,查看偏移与颜色编码
  3. 向上钻取:Choreographer#doFrameRenderThreadqueueBufferacquire/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 10 及以下“Per-display VSYNC 不支持”;Android 11+ 该限制在框架/HWC 能力层面已移除,但应用侧 Choreographer 的请求路径在 main 分支仍有“internal display”相关注释,需结合目标系统分支实测判断
  • Perfetto:按显示 ID 过滤相关 Counter/Slice

Perfetto 实战 Checklist(建议按序查看)

  1. Vsync 信号与周期
  • vsync-app / vsync-sf / vsync-appSf 间隔是否稳定(60/90/120Hz 对应周期)
  • 是否存在异常密集/稀疏的 Vsync(预测抖动)
    image-20250918223148748
  1. Vsync 相位差配置
  • VsyncWorkDuration 是否符合机型预期的 app/sf Offset
  • app 与 sf 的先后是否匹配“先绘制后合成”的策略
    image-20250918222707300
  1. FrameTimeline 判读
  • 先看 PresentType,再看 JankType;确认是 app 还是 SF/GPU 侧问题
  • 选择目标 Surface/FrameToken 定位具体帧
    image-20250918223220718
  1. 应用主线程与渲染线程
  • Choreographer#doFrame 各阶段耗时(Input/Animation/Traversal)
  • RenderThreadsyncAndDrawFrame/DrawFrame 耗时是否异常
    image-20250918223340940
  1. BufferQueue 与 Fence
  1. 合成策略与显示
  • SF 是否频繁走 ClientComposition;HWC validate/present 是否异常
  • 多显示/模式切换/VRR 时是否伴随明显预测偏差
    image-20250918223517315
  1. 资源与其他干扰
  • CPU 竞争(大核占用)、GPU 忙、IO/内存抖动(GC/compaction)
  • 其他前台应用/系统服务是否占用关键资源
    image-20250918223532482

参考文档

  1. Android Graphics Architecture
  2. VSYNC Implementation Guide
  3. Frame Pacing
  4. Perfetto Documentation
  5. Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程
  6. Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战
  7. Vsync offset 相关技术分析
  8. Android 13/14高版本SurfaceFlinger出现VSYNC-app/VSYNC-appSf/VSYNC-sf剖析
  9. AOSP - Choreographer.java(main)
  10. AOSP - android_view_DisplayEventReceiver.cpp(main)
  11. AOSP - DisplayEventDispatcher.h(main)
  12. AOSP - DisplayEventReceiver.cpp(main)
  13. AOSP - VSyncDispatch.h(main)
  14. AOSP - EventThread.cpp(main)
  15. Android Multi-display(官方文档)
  16. AOSP - VsyncConfiguration.h(main)
  17. AOSP - DisplayEventDispatcher.cpp(main)
  18. Unsignaled buffer latch(官方文档)

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球 : 欢迎加入,多谢支持~

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫