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 | 生产端 | 测量、布局、绘制、提交 Buffer | UI 线程耗时、GPU 绘制耗时 |
| BufferQueue | 缓冲管理 | dequeue / queue / acquire / release | 缓冲数量、fence 等待、阻塞 |
| SurfaceFlinger | 系统合成 | 图层合成、VSync 调度、输出 | 合成耗时、图层数量、掉帧 |
| HWC | 硬件合成 | 设备合成 / GPU 合成决策 | Overlay 能力、带宽、功耗 |
| Display Controller | 显示输出 | 逐行扫描、刷新率切换 | 刷新率、扫描周期、撕裂 |
五、一帧的完整信号链
把上述概念串起来,一帧从产生到显示大致经历:VSync 到达 → 应用开始渲染 → 渲染完成入队 BufferQueue → SurfaceFlinger 收到 VSync 后合成 → 提交给 HWC → HWC 在下一个 VSync 切换缓冲区 → 显示控制器逐行扫描输出。任何一环超时,都会让这一帧错过显示窗口。
六、案例与常见误区
假设一台 60Hz 手机,应用某帧渲染耗时 20ms。VSync 周期是 16.67ms,这一帧无法在本次 VSync 前入队,只能等到下一个 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 合成策略时,才能把“卡顿”定位到具体环节,而不是停留在“感觉不流畅”的层面。