2. Android显示系统基础

理解 Android 渲染性能,必须先建立一套“从应用到像素”的显示系统心智模型。很多卡顿、撕裂、掉帧问题,并不是代码写得慢,而是应用产出的帧节奏与屏幕消费帧的节奏没有对齐。本节围绕六个基础概念展开:屏幕刷新率、帧率、垂直同步、逐行扫描、显示缓冲区,以及 SurfaceFlinger 与 HWC 的分工。

一、屏幕刷新率与帧率:消费端与生产端

屏幕刷新率(Refresh Rate)是显示硬件的固有属性,表示屏幕每秒重新绘制画面的次数,单位是 Hz。60Hz 表示每 16.67ms 刷新一次,90Hz 约 11.11ms,120Hz 约 8.33ms。它是“消费端”的节奏,由物理面板和显示控制器决定。

帧率(Frame Rate)是内容生产端每秒提交的画面数量,单位是 fps。游戏渲染、视频解码、UI 动画都会产生帧。帧率可以高于、等于或低于刷新率,但最终显示效果受刷新率约束。

关键结论:刷新率决定“最快能显示多流畅”,帧率决定“实际产出了多少帧”。两者不匹配时,就会出现掉帧、重复帧或抖动。

二、垂直同步与逐行扫描

显示器采用逐行扫描方式成像:电子束或等效驱动逻辑从左上角开始,一行一行地把像素写到右下角,写完一整屏称为一个“扫描周期”。在扫描过程中如果缓冲区内容被修改,屏幕上半部分可能是旧帧、下半部分是新帧,这就是画面撕裂(Tearing)。

垂直同步(VSync)就是解决撕裂的机制。它把帧的提交时机锁定在两次扫描之间的“垂直消隐期”,此时屏幕不显示有效像素,可以安全切换缓冲区。Android 的 VSync 信号由显示子系统产生,分发给应用、SurfaceFlinger 和 HWC,形成统一的节拍源。

VSync 带来稳定,也带来等待。如果一帧没能在 VSync 前完成,就要再等一个周期,表现为掉帧。因此 Android 引入了多级 VSync 偏移(VsyncOffset)和相位对齐,让应用、合成、显示三段流水线错峰工作。

三、显示缓冲区与双缓冲/三缓冲

显示缓冲区(Buffer)是存放一帧像素数据的内存块。应用渲染到 Buffer,SurfaceFlinger 合成 Buffer,HWC 把最终 Buffer 送给显示控制器扫描输出。

  • 单缓冲:生产与消费共用一块,必然撕裂,已淘汰。
  • 双缓冲:前台缓冲正在显示,后台缓冲用于渲染,VSync 时交换。若渲染超时,会阻塞等待。
  • 三缓冲:增加一块备用缓冲,允许渲染排队,减少因偶发超时导致的掉帧,但会增加一帧延迟。

Android 通过 BufferQueue 管理缓冲区的申请、入队、出队和释放,并用 fence(栅栏)同步 GPU、HWC 与显示控制器之间的读写顺序,避免“写未完成就被读”。

四、SurfaceFlinger 与 HWC 的分工

SurfaceFlinger 是 Android 的系统合成服务,负责收集各应用窗口的 Surface,按 Z 序、透明度、裁剪区域进行合成,输出到显示设备。它决定“谁和谁叠在一起、怎么叠”。

HWC(Hardware Composer) 是显示硬件的抽象层,负责把合成任务尽量交给显示控制器硬件完成。HWC 可以决定某些图层由 GPU 合成(Client Composition),某些图层由硬件叠加器直接合成(Device Composition),从而降低 GPU 和带宽开销。

组件角色关键职责性能关注点
应用 / RenderThread生产端测量、布局、绘制、提交 BufferUI 线程耗时、GPU 绘制耗时
BufferQueue缓冲管理dequeue / queue / acquire / release缓冲数量、fence 等待、阻塞
SurfaceFlinger系统合成图层合成、VSync 调度、输出合成耗时、图层数量、掉帧
HWC硬件合成设备合成 / GPU 合成决策Overlay 能力、带宽、功耗
Display Controller显示输出逐行扫描、刷新率切换刷新率、扫描周期、撕裂

五、一帧的完整信号链

把上述概念串起来,一帧从产生到显示大致经历:VSync 到达 → 应用开始渲染 → 渲染完成入队 BufferQueue → SurfaceFlinger 收到 VSync 后合成 → 提交给 HWC → HWC 在下一个 VSync 切换缓冲区 → 显示控制器逐行扫描输出。任何一环超时,都会让这一帧错过显示窗口。

Android 显示系统:一帧从生产到扫描输出的信号链 VSync 信号 16.67ms / 11.11ms 应用渲染 UI / RenderThread BufferQueue dequeue / queue SurfaceFlinger 图层合成 HWC 硬件合成决策 显示控制器逐行扫描 扫描周期内切换缓冲区 → 避免撕裂 关键约束: 应用渲染 ≤ 一个 VSync 周期;SurfaceFlinger 合成 ≤ 一个 VSync 周期;HWC 切换必须在垂直消隐期完成。 任一环节超时 → 掉帧 / 重复帧

六、案例与常见误区

假设一台 60Hz 手机,应用某帧渲染耗时 20ms。VSync 周期是 16.67ms,这一帧无法在本次 VSync 前入队,只能等到下一个 VSync 才被合成,用户感知为一次明显卡顿。若开启三缓冲,渲染可以继续排队,但显示延迟会增加约一帧。

常见误区一:把“帧率”和“刷新率”混为一谈。应用跑 120fps,屏幕只有 60Hz,实际仍按 60Hz 显示,多出的帧被丢弃或排队。
常见误区二:认为“没有撕裂就等于流畅”。VSync 只保证不撕裂,不保证不卡顿。掉帧、重复帧、合成超时都会造成视觉抖动。
排查思路:先用 dumpsys SurfaceFlinger 观察合成耗时与图层数量,再用 dumpsys gfxinfo 查看应用帧耗时分布,最后结合 Perfetto 的 VSync 与 fence 时间线定位是应用、合成还是显示环节超时。
# 查看显示刷新率与合成信息
adb shell dumpsys SurfaceFlinger | grep -i "refresh\|vsync\|hwc"

# 查看应用帧耗时
adb shell dumpsys gfxinfo com.example.app framestats

# 抓取 Perfetto 时间线
adb shell perfetto -o /data/misc/perfetto-traces/trace.pftrace -t 10s \
  sched freq idle am wm gfx view binder_driver hal dalvik camera input res

掌握这些基础概念后,后续分析 VSync 调度、Choreographer、RenderThread、BufferQueue 深度、HWC 合成策略时,才能把“卡顿”定位到具体环节,而不是停留在“感觉不流畅”的层面。