Android 渲染机制与 VSync 归纳总结

讲到 SystemUI 的启动帧率监控,绝绕不开 Android 的渲染机制和 VSync。我见过不少同学上来就抓 Trace,看到掉帧就一脸懵,说白了就是没搞明白底层这个「拧螺丝」的过程。今天咱们把这块掰开揉碎,你理解了这些,再去看 Trace 里的掉帧段就会一目了然。

渲染流水线:CPU → GPU → 屏幕

Android 每帧的渲染大致分三步走:

  1. CPU 阶段:处理输入事件、布局测量、绘制指令(View.draw 的调用),最后生成 DisplayList。
  2. GPU 阶段:接收 DisplayList,进行栅格化,输出到帧缓冲(Buffer)。
  3. 屏幕刷新:从 Buffer 取数据,按像素刷新显示。

这三步不是任意时刻都能同时跑的。硬件会固定发一个信号告诉系统:「老哥,该换下一帧了!」这个信号就是 VSync(垂直同步)。

个人经验: 我早期在优化 Launcher 启动时,遇到过一个诡异的卡顿——每过几秒掉一帧。查了半天发现是第三方的自定义 View 在 onDraw 里做了耗时运算,导致 CPU 完不成任务,错过了下一个 VSync。你说巧不巧,就是缺了对 VSync 的理解。

VSync 的作用与信号模型

VSync 的典型频率是 60Hz(也有 90Hz、120Hz)。每次信号到来,系统就启动一帧的渲染。如果没有 VSync,CPU/GPU 可能一帧还没画完,屏幕就强行读取 Buffer,画面就撕裂了。

Android 的「Choreographer」就是靠 VSync 驱动的。它会等待下一个信号到达,然后回调 doFrame,通知应用层开始绘制。

避坑指南: 我曾经在一个项目里,为了「快」,让一个动画直接调用 invalidate,没有经过 Choreographer 排队。结果 VSync 信号来了之后,CPU 正在忙其他线程,导致这一帧被延迟到下一个周期,帧率直接跌到 30fps。后来老老实实用属性动画,才压住掉帧。

双缓冲与三缓冲

为了不让 CPU/GPU 和屏幕抢同一块 Buffer,Android 用了「双缓冲」甚至「三缓冲」。看看这个对比:

缓冲方案 结构 优点 缺点 常见场景
双缓冲 Front Buffer + Back Buffer 简单,无撕裂 如果一帧超时,下一帧会丢 老设备、低端机
三缓冲 Front + Back + Pending 允许一帧延迟,不容易丢帧 增加内存占用,可能带来额外延迟 高帧率、复杂动画

三缓冲其实是在双缓冲基础上多了一个「等待队列」的 Buffer。当 GPU 来不及在 VSync 前完成当前帧,系统可以从第三个 Buffer 里拿一个已经准备好的旧帧直接给屏幕,避免屏幕空转。代价是反应稍微慢一拍。

你想想看,SystemUI 启动时会有大量的 View 创建和绘制,如果 CPU 调度不及时,单靠双缓冲很容易掉帧。很多厂商会默认开三缓冲,但我们做监控时要知道:掉帧不一定是代码慢,可能是缓冲切换机制造成的。

掉帧的本质:超时未完成

每帧必须在 VSync 间隔内完成 CPU + GPU 的工作。如果超了,就会出现「丢帧」或「延迟帧」。在 Systrace 里你看到的 jank 就是这个意思。

常见导致超时的原因:

  • 主线程有耗时操作(比如反射、IO)
  • 过度绘制太多,GPU 负载过重
  • 垃圾回收(GC)阻塞了渲染线程
  • Binder 调用阻塞
我的建议: 监控 SystemUI 启动帧率时,别只盯着平均帧率——要看「卡顿率」和「最大帧耗时」。一旦单帧超过 16ms(60Hz)或 8.3ms(120Hz),就属于掉帧。可以用 FrameMetricsListener 直接拿到每帧的时间戳,配合 Systrace 定位哪个阶段拖了后腿。
Android 渲染流水线与 VSync 时序 VSync 信号 (60Hz) CPU 阶段 测量、布局、绘制命令 帧缓冲 (Front/Back) 双缓冲 / 三缓冲 GPU 阶段 栅格化、合成 屏幕 60Hz 刷新 触发 doFrame 提交 DisplayList 取走渲染 写回成品帧 VSync 读取 三缓冲额外队列 Pending Buffer 暂存, 避免屏幕空等

小结:监控中要关注什么?

搞明白上面这些,你就能站在工程角度判断:

  • 如果 Systrace 显示 App 帧耗时集中在 CPU 阶段——多半是主线程太忙。
  • 如果 GPU 阶段占满——可能是过度绘制或者着色器复杂。
  • 如果 Buffer 切换出现空档(丢帧)——大概率是三缓冲策略没生效,或者内存交换慢。

我习惯在 SystemUI 启动时用 FrameMetricsAggregator 直接记录每帧的 DRAW、SYNC、SWAP_BUFFERS 等阶段耗时。再配合 VSync 时序,一眼就能看出哪一段「拖了全班后腿」。嗯,下节课我们就专门讲怎么用这些工具做监控。