Android Performance

Android Perfetto 系列 8:深入理解 Vsync 机制与性能分析

Word count: 6.3kReading time: 26 min
2025/08/05
loading

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

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

注:本文内容基于 Android 16 的最新架构和实现

系列文章目录

  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. 视频(B站) - Android Perfetto 基础和案例分享

如果大家还没看过 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
// frameworks/base/core/java/android/view/Choreographer.java
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
// 向系统申请下一个 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 传递,时间可能略晚于 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
// 1. UI 组件请求重绘
// frameworks/base/core/java/android/view/View.java
public void invalidate() {
// 标记为需要重绘,但不立即执行
mPrivateFlags |= PFLAG_DIRTY;

if (mParent != null && mAttachInfo != null) {
// 向父容器请求重绘
mParent.invalidateChild(this, null);
}
}

// 2. ViewRootImpl 调度遍历
// frameworks/base/core/java/android/view/ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 关键:向 Choreographer 注册回调,等待下一个 Vsync
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}

// 3. Choreographer 申请 Vsync
// frameworks/base/core/java/android/view/Choreographer.java
public void postCallback(int callbackType, Runnable action, Object token) {
// 添加回调到队列
mCallbackQueues[callbackType].addCallbackLocked(action, token);

if (callbackType == CALLBACK_TRAVERSAL) {
// 如果是 UI 遍历回调,立即申请 Vsync
scheduleFrameLocked(now);
}
}

private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
// 关键:向系统申请 Vsync 信号
mDisplayEventReceiver.scheduleVsync();
}
}

主线程如何监听 Vsync 信号

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

1. 建立连接

1
2
3
4
5
6
7
8
9
// frameworks/base/core/java/android/view/Choreographer.java
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {

public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
// 在构造时建立与 SurfaceFlinger 的连接
}
}

2. 接收 Vsync 信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
// 接收到 Vsync 信号,但注意:这里并不直接执行 doFrame
mTimestampNanos = timestampNanos;
mFrame = frame;

// 关键:将工作 post 到主线程的 MessageQueue 中
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true); // 设为异步消息,优先处理
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}

@Override
public void run() {
// 这里才真正开始执行一帧的工作
doFrame(mTimestampNanos, mFrame);
}

几个遗留问题

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

  • 线程边界:onVsync() 可能不在主线程,UI 必须在主线程执行
  • 调度控制:通过 sendMessageAtTime() 精确对齐执行时刻
  • 队列语义:进入主线程 MessageQueue,确保与其他高优先级任务协同

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

  • 不会。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 机制presentFence 是连接 GPU 渲染和 SurfaceFlinger 合成的关键同步机制。根据系统 Latch Unsignaled Buffers 策略,SurfaceFlinger 并非总是等待 GPU 完成,而是可以“抢跑”提前开始合成,仅在最终需要使用 Buffer 内容时才等待 fence 信号,以此来隐藏延迟。

    image-20250918222626100

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

  • 提升跟手性:通过调整 sf vsync 的相位差,可以让应用从开始绘制到显示在屏幕上的时间从 3 个 Vsync 周期缩短到 2 个 Vsync 周期。这对于触摸响应等交互场景非常重要。
  • 解决应用绘制超时问题:当应用绘制超时时,合理的 sf 相位差可以为应用争取更多的处理时间,避免因为时序不当导致的掉帧。
  • 通过观察 Perfetto 中的 VsyncWorkDuration 指标可以了解系统的 Vsync Offset 配置。
  • 下图中显示的时间段就是我手上的手机配置的 app offset (13.3ms)

image-20250918222707300

Vsync Offset 的技术实现

在 Android 系统中,Vsync Offset 是通过 VsyncConfiguration 来实现的:

1
2
3
4
5
6
7
8
9
10
11
// frameworks/native/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
struct VsyncConfiguration {
// 应用 Vsync 相对于硬件 Vsync 的偏移
nsecs_t appOffset;
// SurfaceFlinger Vsync 相对于硬件 Vsync 的偏移
nsecs_t sfOffset;
// 早期应用 Vsync 偏移(用于特殊情况)
nsecs_t appOffsetEarly;
// 早期 SurfaceFlinger Vsync 偏移
nsecs_t sfOffsetEarly;
};

关键概念

  • appOffset:应用 Vsync 相对于硬件 Vsync 的时间偏移
  • sfOffset:SurfaceFlinger Vsync 相对于硬件 Vsync 的时间偏移
  • Vsync Offset = sfOffset - appOffset:应用和 SurfaceFlinger 接收信号的时间差

实际的优化效果

以 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 信号从硬件传递到应用层的完整链路如下。

Native 层:Vsync 信号的产生与管理

硬件 Vsync 的产生

Vsync 信号最初由显示硬件产生。在 HWC(Hardware Composer)层面,硬件会定期产生 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
// hardware/interfaces/graphics/composer/2.1/utils/hwc2on1adapter/HWC2On1Adapter.cpp
// 注意:这是 HWC 1.x 到 2.x 的兼容性适配器,现代设备通常使用原生 HWC 2.x+
// 基于 Android 16 AOSP 最新代码
void HWC2On1Adapter::vsyncThread() {
while (!mVsyncEnded) {
if (mVsyncEnabled) {
const auto now = std::chrono::steady_clock::now();
const auto vsyncPeriod = std::chrono::nanoseconds(mVsyncPeriod);
const auto nextVsync = mLastVsync + vsyncPeriod;

if (now >= nextVsync) {
// 产生硬件 Vsync 时间戳
auto timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(
nextVsync.time_since_epoch()).count();

// 回调到 SurfaceFlinger
if (mVsyncCallback) {
mVsyncCallback->onVsync(timestamp, mDisplayId);
}
mLastVsync = nextVsync;
}
}
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}

VsyncController:核心控制器

VsyncController 是整个 Vsync 系统的大脑,它接收硬件 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
// frameworks/native/services/surfaceflinger/Scheduler/VsyncController.h
// 实际实现通常在 VSyncReactor.cpp 中
// 基于 Android 16 AOSP 最新代码
void VsyncController::onVsync(nsecs_t timestamp, int32_t hwcDisplayId) {
std::lock_guard<std::mutex> lock(mMutex);

// 记录硬件 Vsync 时间戳到追踪器
mVsyncTracker->addVsyncTimestamp(timestamp);

// 检查是否需要更多硬件 Vsync 样本来校准预测模型
if (mVsyncTracker->needsMoreSamples()) {
// 模型还需要更多样本,继续启用硬件 Vsync
ALOGV("VsyncController: Need more samples, keeping HW Vsync enabled");
} else {
// 模型已经稳定,可以关闭硬件 Vsync 节省电量
ALOGV("VsyncController: Model stable, disabling HW Vsync");
enableHardwareVsync(false);
}

// 通知所有等待的客户端
for (auto& callback : mCallbacks) {
callback->onVsync(timestamp);
}
}

void VsyncController::enableHardwareVsync(bool enable) {
if (mHwVsyncEnabled != enable) {
mHwVsyncEnabled = enable;
// 通过 HWC 接口控制硬件 Vsync
mHwc.setVsyncEnabled(mDisplayId, enable);

// 在 Perfetto trace 中可以看到这个状态变化
ATRACE_INT("HW_VSYNC", enable ? 1 : 0);
}
}

VsyncDispatch:信号分发机制

VsyncDispatch 负责将 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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// frameworks/native/services/surfaceflinger/Scheduler/VSyncDispatch.h
// 实际实现在 VSyncDispatchTimerQueue.cpp 中
// 基于 Android 16 AOSP 最新代码
VsyncDispatch::CallbackToken VsyncDispatch::registerCallback(
Callback callback, std::string callbackName) {
std::lock_guard<decltype(mMutex)> lock(mMutex);

// 为每个回调生成唯一的 token
auto token = CallbackToken(mCallbackMap.size());
mCallbackMap[token] = std::make_unique<CallbackEntry>(
std::move(callback), std::move(callbackName));

return token;
}

nsecs_t VsyncDispatch::schedule(CallbackToken token,
nsecs_t workDuration,
nsecs_t earliestVsync) {
std::lock_guard<decltype(mMutex)> lock(mMutex);

auto it = mCallbackMap.find(token);
if (it == mCallbackMap.end()) {
return kInvalidTime;
}

// 计算目标 Vsync 时间
auto targetVsync = mVsyncTracker->nextVsyncTime(earliestVsync);

// 计算唤醒时间 = 目标 Vsync - 工作时长
auto wakeupTime = targetVsync - workDuration;

// 如果唤醒时间已经过了,推到下一个 Vsync
auto now = systemTime(SYSTEM_TIME_MONOTONIC);
if (wakeupTime < now) {
targetVsync = mVsyncTracker->nextVsyncTime(targetVsync + 1);
wakeupTime = targetVsync - workDuration;
}

// 设置回调的触发时间
it->second->wakeupTime = wakeupTime;
it->second->targetVsync = targetVsync;
it->second->workDuration = workDuration;

// 重新调度定时器
reschedule();

return targetVsync;
}

void VsyncDispatch::reschedule() {
// 找到最早需要触发的回调
nsecs_t nextWakeup = std::numeric_limits<nsecs_t>::max();
for (const auto& [token, entry] : mCallbackMap) {
if (entry->wakeupTime > 0 && entry->wakeupTime < nextWakeup) {
nextWakeup = entry->wakeupTime;
}
}

if (nextWakeup != std::numeric_limits<nsecs_t>::max()) {
// 设置定时器在 nextWakeup 时间触发
mTimeKeeper->alarmAt(std::bind(&VsyncDispatch::timerCallback, this), nextWakeup);
}
}

void VsyncDispatch::timerCallback() {
std::vector<std::pair<CallbackToken, CallbackEntry*>> firedCallbacks;

{
std::lock_guard<decltype(mMutex)> lock(mMutex);
auto now = systemTime(SYSTEM_TIME_MONOTONIC);

// 找到所有应该触发的回调
for (auto& [token, entry] : mCallbackMap) {
if (entry->wakeupTime > 0 && now >= entry->wakeupTime) {
firedCallbacks.push_back({token, entry.get()});
entry->wakeupTime = 0; // 重置
}
}
}

// 在锁外执行回调,避免死锁
for (auto& [token, entry] : firedCallbacks) {
entry->callback(entry->targetVsync, entry->wakeupTime);
}
}

EventThread:连接 Native 和 Framework

EventThread 是连接 Native 层 Vsync 系统和 Framework 层的桥梁:

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
50
51
52
53
54
55
// frameworks/native/services/surfaceflinger/Scheduler/EventThread.cpp
EventThread::EventThread(std::unique_ptr<VSyncSource> vsyncSource,
std::unique_ptr<InterceptVSyncCallback> interceptVSyncCallback,
const char* threadName)
: mVSyncSource(std::move(vsyncSource))
, mInterceptVSyncCallback(std::move(interceptVSyncCallback))
, mThreadName(threadName) {

// 向 VsyncDispatch 注册回调
mVSyncSource->setCallback(this);

// 启动 EventThread 线程
mThread = std::thread([this] { threadMain(); });
}

void EventThread::threadMain() {
std::unique_lock<std::mutex> lock(mMutex);

while (mKeepRunning) {
// 等待 Vsync 事件或连接变化
mCondition.wait(lock, [this] {
return !mPendingEvents.empty() || !mKeepRunning;
});

// 处理所有待处理的事件
auto pendingEvents = std::move(mPendingEvents);
lock.unlock();

for (const auto& event : pendingEvents) {
// 分发给所有连接的客户端
for (const auto& connection : mConnections) {
if (connection->vsyncRequest != VSyncRequest::None) {
connection->postEvent(event);
}
}
}

lock.lock();
}
}

void EventThread::onVSyncEvent(nsecs_t timestamp, int32_t displayId) {
std::lock_guard<std::mutex> lock(mMutex);

// 创建 Vsync 事件
DisplayEventReceiver::Event event;
event.header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
event.header.id = displayId;
event.header.timestamp = timestamp;
event.vsync.count = ++mVSyncCount;

// 添加到待处理事件队列
mPendingEvents.push_back(event);
mCondition.notify_all();
}

Framework 层:Vsync 信号的接收与处理

DisplayEventReceiver:Java 和 Native 的桥梁

DisplayEventReceiver 是 Framework 层接收 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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
jobject messageQueueObj, jint vsyncSource, jint configChanged) {

// 获取 Java 层的 MessageQueue
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);

// 创建 Native 层的 DisplayEventReceiver
sp<NativeDisplayEventReceiver> receiver = new NativeDisplayEventReceiver(
env, receiverWeak, messageQueue, vsyncSource, configChanged);

// 注册到 EventThread
status_t status = receiver->initialize();
if (status) {
ALOGE("Failed to initialize display event receiver, status=%d", status);
return 0;
}

receiver->incStrong(gDisplayEventReceiverClassInfo.clazz);
return reinterpret_cast<jlong>(receiver.get());
}

class NativeDisplayEventReceiver : public DisplayEventReceiver, public MessageHandler {
public:
NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
const sp<MessageQueue>& messageQueue,
jint vsyncSource, jint configChanged)
: DisplayEventReceiver(static_cast<ISurfaceComposer::VsyncSource>(vsyncSource),
static_cast<ISurfaceComposer::ConfigChanged>(configChanged))
, mReceiverWeakGlobal(env->NewGlobalWeakRef(receiverWeak))
, mMessageQueue(messageQueue) {
}

// 当 Vsync 事件到达时的回调
virtual void onVsync(nsecs_t timestamp, nsecs_t physicalDisplayId, uint32_t count) override {
ALOGV("NativeDisplayEventReceiver: onVsync timestamp=%lld", (long long)timestamp);

// 将事件保存并 post 到 MessageQueue
mTimestamp = timestamp;
mDisplayId = physicalDisplayId;
mCount = count;

// 发送消息到 Java 层的 MessageQueue
mMessageQueue->getLooper()->sendMessage(this, Message(MSG_VSYNC));
}

// MessageHandler 的实现,在 Java 层 Looper 中执行
virtual void handleMessage(const Message& message) override {
switch (message.what) {
case MSG_VSYNC:
// 调用 Java 层的 onVsync 方法
dispatchVsync(mTimestamp, mDisplayId, mCount);
break;
}
}

private:
void dispatchVsync(nsecs_t timestamp, nsecs_t displayId, uint32_t count) {
JNIEnv* env = AndroidRuntime::getJNIEnv();

jobject receiverObj = env->NewLocalRef(mReceiverWeakGlobal);
if (receiverObj) {
// 调用 Java 层 DisplayEventReceiver.onVsync()
env->CallVoidMethod(receiverObj, gDisplayEventReceiverClassInfo.onVsync,
ns2ms(timestamp), displayId, count);
env->DeleteLocalRef(receiverObj);
}
}
};

Choreographer 中的 Vsync 处理

在 Java 层,Choreographer 的 FrameDisplayEventReceiver 负责处理 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
50
51
52
53
// frameworks/base/core/java/android/view/Choreographer.java
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {

private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;

public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}

// Native 层回调到这里
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
// 检查是否有重复的 Vsync(防御性编程)
if (mHavePendingVsync) {
Log.w(TAG, "Already have a pending vsync event. There should only be one"
+ " at a time.");
} else {
mHavePendingVsync = true;
}

// 保存 Vsync 信息
mTimestampNanos = timestampNanos;
mFrame = frame;

// 关键:将处理工作 post 到主线程的 MessageQueue
// 注意这里使用了 timestampNanos 作为执行时间
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true); // 异步消息,有更高优先级
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}

// Runnable 接口的实现,在主线程中执行
@Override
public void run() {
mHavePendingVsync = false;
// 开始处理一帧的工作
doFrame(mTimestampNanos, mFrame);
}
}

// Choreographer 处理一帧的核心逻辑(简化摘录,详见 AOSP 源码)
void doFrame(long frameTimeNanos, int frame) {
// … 省略前置检查与时间校正 …
// 按顺序执行五类回调
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}

关键时序点分析

通过上述代码流程,我们可以看到完整的时序链路:

  1. HWC 产生硬件中断VsyncController::onVsync()
  2. VsyncController 处理VsyncDispatch::schedule()
  3. VsyncDispatch 分发EventThread::onVSyncEvent()
  4. EventThread 通知NativeDisplayEventReceiver::onVsync()
  5. Native 传递到 JavaFrameDisplayEventReceiver::onVsync()
  6. 主线程处理Choreographer::doFrame()

各环节的职责和优化点不同,理解完整流程有助于在 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 源独立,注意 app/sf/appSf 的选择与对齐;
    • Perfetto:按显示 ID 过滤相关 Counter/Slice

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

  1. Vsync 信号与周期
    • vsync-app / vsync-sf / vsync-appSf 间隔是否稳定(60/90/120Hz 对应周期)
    • 是否存在异常密集/稀疏的 Vsync(预测抖动)
      image-20250918223148748
  2. Vsync 相位差配置
    • VsyncWorkDuration 是否符合机型预期的 app/sf Offset
    • app 与 sf 的先后是否匹配“先绘制后合成”的策略
      image-20250918222707300
  3. FrameTimeline 判读
    • 先看 PresentType,再看 JankType;确认是 app 还是 SF/GPU 侧问题
    • 选择目标 Surface/FrameToken 定位具体帧
      image-20250918223220718
  4. 应用主线程与渲染线程
    • Choreographer#doFrame 各阶段耗时(Input/Animation/Traversal)
    • RenderThreadsyncAndDrawFrame/DrawFrame 耗时是否异常
      image-20250918223340940
  5. BufferQueue 与 Fence
    • 生产者:RenderThread queueBuffer 之后,这个 Buffer 就被标记可以被消费了,在经过一次 Transaction 之后就可以被 sf 消费。刚刚 queueBuffer 之后 Buffer 还需要让 GPU 实际进行渲染,这里是用的 Fence 来追踪 GPU 的执行耗时。SF 也需要看这个 Fence 来决定这个 Buffer 是不是 Ready (新版本 SF 有个属性值配置,也可以不等这个 Fence,等后面实际要合成的时候才去看)。
      image-20250918224205093
    • 消费者 SF 与 BufferTX:SF 作为 Buffer 的消费者,会在 vsync 到来的时候是会取一个 Buffer 进行合成的,这里 App 的 Buffer 是以 BufferTX-xxxx 的名字存在的,下图可以看到每次 SF 取走一个 Buffer,BufferTX 个数就会减1,这就是正常的。如果 BufferTX 为 0,说明 App 没有及时送 Buffer 上来,SF 也就停止合成了,这就会出现卡顿 (当然如果同时有多个出图源,SF 会取其他的 Buffer,但是对于 App 来说依然是卡顿的)
      image-20250918223441117
  6. 合成策略与显示
    • SF 是否频繁走 ClientComposition;HWC validate/present 是否异常
    • 多显示/模式切换/VRR 时是否伴随明显预测偏差
      image-20250918223517315
  7. 资源与其他干扰
    • 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剖析

关于我 && 博客

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

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

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

微信扫一扫

CATALOG
  1. 1. 系列文章目录
    1. 1.1. 什么是 Vsync
      1. 1.1.1. Vsync 解决什么问题?
    2. 1.2. Android 中 Vsync 的基本工作原理
      1. 1.2.1. Vsync 信号的分层架构
    3. 1.3. 在 Perfetto 中观察 Vsync
    4. 1.4. Android App 每一帧是如何基于 Vsync 工作的
      1. 1.4.1. 流程总览(按顺序)
      2. 1.4.2. App 什么时候会申请 Vsync 信号
      3. 1.4.3. App 申请 Vsync 的完整流程
      4. 1.4.4. 主线程如何监听 Vsync 信号
      5. 1.4.5. 几个遗留问题
      6. 1.4.6. Vsync Offset 的技术实现
      7. 1.4.7. 实际的优化效果
    5. 1.5. Vsync 信号的完整代码流程
      1. 1.5.1. Native 层:Vsync 信号的产生与管理
      2. 1.5.2. Framework 层:Vsync 信号的接收与处理
      3. 1.5.3. 关键时序点分析
      4. 1.5.4. FrameTimeline
      5. 1.5.5. 刷新率/显示模式/VRR 对 Vsync 与 Offset/预测的影响
      6. 1.5.6. Perfetto 实战 Checklist(建议按序查看)
    6. 1.6. 参考文档
  2. 2. 关于我 && 博客