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

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

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

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

本文目录

  • 系列文章目录
  • [什么是 Vsync](#什么是 Vsync "#what-is-vsync")
  • [Android 中 Vsync 的基本工作原理](#Android 中 Vsync 的基本工作原理 "#principle")
  • [在 Perfetto 中观察 Vsync](#在 Perfetto 中观察 Vsync "#perfetto-observe")
  • [Android App 每一帧是如何基于 Vsync 工作的](#Android App 每一帧是如何基于 Vsync 工作的 "#app-frames")
  • 参考文档
  • [关于我 && 博客](#关于我 && 博客 "#about")

系列文章目录

  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. 视频(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 信号。

arduino 复制代码
// 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 信号。

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

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

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

在应用进程中

FrameDisplayEventReceiver.onVsync Slice Track: 显示应用接收 Vsync 信号的时间点。由于信号通过 Binder 传递,时间可能略晚于 SurfaceFlinger 中的 vsync-app

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

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

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

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

流程总览(按顺序)

  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 信号:

typescript 复制代码
// 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. 建立连接

scala 复制代码
// 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 信号

java 复制代码
@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()

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

Q2: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 ,则还是会出现掉帧。

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

  • GPU 渲染是异步的,这带来了额外的复杂性:

    • CPU 工作正常,GPU 成为瓶颈:即使应用主线程在 Vsync 周期内完成工作,GPU 渲染耗时过长仍会导致掉帧
    • GPU Fence 机制presentFence 是连接 GPU 渲染和 SurfaceFlinger 合成的关键同步机制。根据系统 Latch Unsignaled Buffers 策略,SurfaceFlinger 并非总是等待 GPU 完成,而是可以"抢跑"提前开始合成,仅在最终需要使用 Buffer 内容时才等待 fence 信号,以此来隐藏延迟。

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

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

Vsync Offset 的技术实现

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

arduino 复制代码
// 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 中断:

c 复制代码
// 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 并管理软件预测:

scss 复制代码
// 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 信号精确地分发给不同的消费者,每个消费者可以有不同的触发时间:

ini 复制代码
// 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 层的桥梁:

arduino 复制代码
// 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 信号的关键组件:

scss 复制代码
// 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 信号:

java 复制代码
// 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

  • 轨道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(预测抖动)
  2. Vsync 相位差配置

    • VsyncWorkDuration 是否符合机型预期的 app/sf Offset
    • app 与 sf 的先后是否匹配"先绘制后合成"的策略 !
  3. FrameTimeline 判读

    • 先看 PresentType,再看 JankType;确认是 app 还是 SF/GPU 侧问题
    • 选择目标 Surface/FrameToken 定位具体帧 !
  4. 应用主线程与渲染线程

    • Choreographer#doFrame 各阶段耗时(Input/Animation/Traversal)
    • RenderThreadsyncAndDrawFrame/DrawFrame 耗时是否异常
  5. BufferQueue 与 Fence

    • 生产者:RenderThread queueBuffer 之后,这个 Buffer 就被标记可以被消费了,在经过一次 Transaction 之后就可以被 sf 消费。刚刚 queueBuffer 之后 Buffer 还需要让 GPU 实际进行渲染,这里是用的 Fence 来追踪 GPU 的执行耗时。SF 也需要看这个 Fence 来决定这个 Buffer 是不是 Ready (新版本 SF 有个属性值配置,也可以不等这个 Fence,等后面实际要合成的时候才去看)。
    • 消费者 SF 与 BufferTX:SF 作为 Buffer 的消费者,会在 vsync 到来的时候是会取一个 Buffer 进行合成的,这里 App 的 Buffer 是以 BufferTX-xxxx 的名字存在的,下图可以看到每次 SF 取走一个 Buffer,BufferTX 个数就会减1,这就是正常的。如果 BufferTX 为 0,说明 App 没有及时送 Buffer 上来,SF 也就停止合成了,这就会出现卡顿 (当然如果同时有多个出图源,SF 会取其他的 Buffer,但是对于 App 来说依然是卡顿的)
  6. 合成策略与显示

    • SF 是否频繁走 ClientComposition;HWC validate/present 是否异常
    • 多显示/模式切换/VRR 时是否伴随明显预测偏差
  7. 资源与其他干扰

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

参考文档

  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性能优化知识星球 : 欢迎加入,多谢支持~

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

相关推荐
Gracker2 小时前
Android Perfetto 系列 07 - MainThread 和 RenderThread 解读
android
Gracker2 小时前
Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程
android
Gracker2 小时前
Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战
android
apigfly3 小时前
Flutter,Compose,Web 在Android平台上从布局到屏幕的机制探究
android·flutter·webview
czhc11400756634 小时前
Java1112 基类 c#vscode使用 程序结构
android·java·数据库
從南走到北5 小时前
JAVA国际版打车APP打车顺风车滴滴车跑腿APP源码Android+IOS+H5
android·java·ios
独自破碎E5 小时前
从括号匹配到字符串解码:递归思想的巧妙应用
android·java·开发语言
只想搞钱的肥仔6 小时前
Android thermal (7)_thermal core
android
一氧化二氢.h6 小时前
MySQL root用户连接错误解决方法
android·数据库·mysql