一、先记住这张总图
业务代码修改 View
│
▼
invalidate / requestLayout
│
▼
Choreographer 等待 App VSync
│
▼
ViewRootImpl.performTraversals()
│
├── measure:确定尺寸
├── layout :确定位置
└── draw :记录绘制指令
│
▼
RenderThread
│
▼
GPU/Skia 渲染到 Buffer
│
▼
Surface.queueBuffer → BufferQueue
│
▼
SurfaceFlinger
│
收集系统所有可见 Layer
│
▼
HWC 决定每层的合成方式
│ │
│ │
CLIENT 合成 DEVICE 合成
GPU 合成 DPU/Overlay
└──────┬───────┘
▼
Present/送显
│
▼
屏幕
这里必须牢牢记住两种动作:
- 渲染:把某个图层的绘制命令变成像素。
- 合成:决定多个图层最终如何叠加显示。
例如:
- App 把按钮、文字、图片画进自己的窗口 Buffer,是渲染。
- SurfaceFlinger 把 App、状态栏、导航栏、壁纸叠起来,是合成。
要以“渲染—合成—送显”作为主线,Android 官方则把系统核心概括为 BufferQueue、SurfaceFlinger 和 Hardware Composer。AOSP Graphics Architecture
二、一帧究竟是怎样产生的
1. View 发生变化
典型入口是:
view.invalidate();
view.requestLayout();
两者不能混为一谈:
| 方法 | 含义 | 通常触发 |
|---|---|---|
invalidate() | 内容变了,需要重画 | draw |
requestLayout() | 尺寸或位置可能变了 | measure + layout + draw |
它们不会立刻完成屏幕绘制,而是通过 ViewRootImpl.scheduleTraversals(),将一次 traversal 安排到下一次合适的帧时机。
这样做是为了合并同一帧中的多次修改。例如连续设置三次文字,系统没必要立即向屏幕提交三帧,中间状态通常可以被合并掉。
2. Choreographer 等待 VSync
Choreographer 可以理解成 UI 线程的帧调度器。
它收到帧时序事件后,按阶段执行:
输入事件
→ 动画
→ View 树遍历
→ 提交渲染
文章重点讲了 traversal callback 和同步屏障:同步屏障使 VSync 驱动的异步消息可以优先于普通同步消息被处理。
但要避免一个误解:
VSync 不是“系统命令 App 必须画一帧”,而是给需要更新画面的参与者提供一个与屏幕刷新对齐的时间基准。
现代 Android 的 Choreographer 还包含 FrameTimeline、deadline 和 expected presentation time 等信息,不再只是一个简单的固定周期回调。Choreographer API
3. ViewRootImpl 遍历 View 树
VSync 回调最终推动:
ViewRootImpl.doTraversal()
└── performTraversals()
├── performMeasure()
├── performLayout()
└── performDraw()
三阶段分别回答三个问题:
- Measure:每个 View 多大?
- Layout:每个 View 放在哪里?
- Draw:它应该画什么?
注意,Measure 并不保证只遍历一次。某些布局可能要求父子之间反复测量,所以复杂嵌套、权重和频繁改变布局参数都会增加成本。
三、硬件加速下,draw 并不等于 GPU 正在画
这是原文最值得进一步校正的地方。
文章说“开启硬件加速后,draw 发生在 GPU”,作为入门近似可以接受,但不够精确。真实过程分成两个阶段:
UI Thread
View.draw(Canvas)
→ 将绘制操作记录为 RenderNode / DisplayList
RenderThread
→ 处理这些绘制命令
→ 使用 Skia/GPU 将内容栅格化到 Buffer
也就是说:
- UI 线程通常负责遍历 View 树、运行自定义
onDraw()、记录绘制命令。 - RenderThread 负责消费绘制命令并准备 GPU 工作。
- GPU 异步执行真正的图形计算。
Android 官方也将硬件加速 UI 渲染分成:
- UI Thread 上的
Record View#draw - RenderThread 上的
DrawFrame
因此,即使启用了硬件加速,下面这些代码仍然会卡 UI 线程:
override fun onDraw(canvas: Canvas) {
// 复杂业务计算
// 创建大量对象
// 解码图片
// 遍历大量数据
}
GPU 只能帮你执行图形工作,不能替你运行这些 Java/Kotlin 业务逻辑。
四、Surface、BufferQueue、GraphicBuffer 到底是什么关系
这是理解 Android 图形系统的核心。
GraphicBuffer:一张可流转的画布
可以把它理解为一块保存像素数据的图形缓冲区。
它可能被不同硬件访问:
- CPU 写入;
- GPU 渲染;
- SurfaceFlinger/GPU 读取并合成;
- HWC/DPU 扫描输出。
但严格来说,并不是“GraphicBuffer 一定由 GPU 分配”。Buffer 通常通过 Gralloc/allocator 体系分配,根据 usage、格式和设备能力,得到 CPU、GPU、显示硬件可访问的内存。
Surface:生产者入口
对于应用来说,Surface 更像一块可以提交图像的输出端口。它代表 BufferQueue 的生产者一侧,而不是“永远固定的一块显存”。
生产者通常执行:
dequeueBuffer
→ 获得一个可写 Buffer
→ 渲染
→ queueBuffer
Java Surface、ANativeWindow、EGL window surface,最终都围绕这个生产过程工作。
BufferQueue:生产者和消费者之间的传送带
典型角色是:
生产者:App / RenderThread / 视频解码器 / Camera
消费者:SurfaceFlinger / SurfaceTexture / ImageReader
经典状态循环:
FREE
↓ dequeueBuffer
DEQUEUED
↓ queueBuffer
QUEUED
↓ acquireBuffer
ACQUIRED
↓ releaseBuffer
FREE
需要注意:
SurfaceFlinger 是常见消费者,但并非所有 BufferQueue 的消费者都是 SurfaceFlinger。
例如:
- Camera → SurfaceTexture:App 是消费者;
- 解码器 → SurfaceView:SurfaceFlinger 是消费者;
- Camera → ImageReader:ImageReader 是消费者。
BufferQueue 是图形数据流转的通用管道,而不仅是“App 到 SurfaceFlinger”的专用队列。AOSP BufferQueue and Gralloc
五、Fence 不是普通互斥锁
原文把 Fence 类比成硬件互斥锁,便于入门,但容易让人形成错误模型。
更准确地说:
Fence 是一张“此前提交的异步工作是否已经完成”的凭证。
比如 GPU 渲染是异步的:
CPU:我已经把渲染命令提交给 GPU 了
GPU:但我还没画完
CPU:先把 Buffer 连同 acquire fence 一起交出去
消费者:等 fence signal 后再读取
Fence 的价值是避免 CPU 阻塞等待,同时确保下游不会过早读取尚未完成的 Buffer。
典型有两类:
- Acquire fence:消费者什么时候可以开始读取。
- Release fence:生产者什么时候可以重新使用这个 Buffer。
它表达的是异步任务之间的依赖关系,不一定意味着“某时刻只能有一个角色碰这块内存”。官方同步框架说明见 AOSP Synchronization Framework。
六、SurfaceFlinger 真正负责什么
SurfaceFlinger 是系统级合成器,主要工作不是替 App 绘制按钮文字,而是:
- 收集各个 Surface 提交的新 Buffer;
- 维护与窗口对应的 Layer;
- 计算 Layer 的 Z 顺序、裁剪、变换、透明度、可见区域;
- 把 Layer 列表交给 HWC 评估;
- 对需要 CLIENT composition 的 Layer 做 GPU 合成;
- 把最终显示配置提交给 HWC;
- 跟踪 present fence 和显示时序。
一个 Surface 通常对应 SurfaceFlinger 侧的一个 Layer,但实际系统比“一窗口一 Layer”更复杂,可能包含容器层、颜色层、镜像层、SurfaceControl 层级等。
七、HWC、DPU、GPU 如何分工
SurfaceFlinger 不会武断地决定所有 Layer 都由 GPU 合成,它会把当前 Layer 列表交给 Hardware Composer HAL。
HWC 根据显示硬件能力,决定每层使用:
DEVICE composition:显示控制器/DPU/overlay plane 直接处理;CLIENT composition:SurfaceFlinger 先用 GPU 合成到 client target,再交给 HWC;- 某些版本还存在 SOLID_COLOR、CURSOR、SIDEBAND 等类型。
可以把协商过程理解为:
SurfaceFlinger:
这里有壁纸、App、视频、状态栏、导航栏。
HWC:
视频和状态栏我可以用硬件平面直接显示;
其余几个请你先用 GPU 合成到一个 client target。
SurfaceFlinger:
好,我把 GPU 合成结果和剩余硬件层一起提交给你。
这里有个重要修正:
“合成”不一定真的生成一张包含所有 Layer 的新大图。
如果 DPU 使用多个 overlay plane,显示控制器可以在扫描输出时直接组合多个 Layer,并不需要事先写出一张完整的合成 Buffer。只有 GPU CLIENT composition 部分通常需要产生一个 client target。
HWC 的职责是抽象不同厂商的显示硬件能力,并不仅仅等于 DPU 本身。AOSP Hardware Composer HAL
八、VSync 怎样让流水线协同
在经典模型中,可以想象存在三个相位:
App VSync
App 开始处理输入、动画并生成下一帧
SF VSync
SurfaceFlinger 开始为即将显示的帧做合成
HW VSync
显示设备进入下一次刷新,呈现已准备好的内容
这些信号周期相同,但相位可以不同。
这意味着流水线可能同时处理不同帧:
屏幕正在显示 N
SurfaceFlinger 正在合成 N+1
App 正在生成 N+2
因此“App 渲染完,下一瞬间屏幕就显示”通常是不成立的,中间还涉及:
- Buffer 入队;
- SurfaceFlinger latch;
- 合成;
- HWC present;
- 显示扫描输出。
官方将 VSync 的目标描述为同步 App 渲染、SurfaceFlinger 合成和屏幕刷新。AOSP VSync
在可变刷新率、FrameTimeline、预测 VSync 和现代调度策略下,真实实现已经比 Android 7 的 DispSync 模型复杂得多,但这套流水线思维仍然有效。
九、双缓冲和三缓冲到底解决什么
如果只有一个 Buffer:
屏幕正在读它
App 就不能同时改它
双缓冲允许:
屏幕读取 A
App 渲染 B
下次交换 A/B
但如果生产者或消费者偶尔来不及,就可能互相等待。三缓冲增加一个周转 Buffer,可以降低流水线停顿概率,让 App、GPU、SurfaceFlinger/显示硬件更容易并行。
代价是:
- 增加图形内存;
- 队列积压时可能增加一帧延迟。
因此三缓冲不是“帧率加倍”,它解决的是流水线并发和抖动容忍度问题。
十、卡顿究竟发生在哪里
不要把所有卡顿都归结为“主线程超过 16.6ms”。
一帧可能在多个阶段失约:
UI Thread
输入/动画/measure/layout/draw 太慢
↓
RenderThread
构建帧、纹理上传、复杂路径处理太慢
↓
GPU
overdraw、shader、像素填充量过大
↓
BufferQueue
没有可用 Buffer,生产者阻塞
↓
SurfaceFlinger
Layer 太多、合成复杂
↓
HWC / Display
present 或 fence 延迟
刷新率对应的理论周期:
| 刷新率 | 单周期 |
|---|---|
| 60 Hz | 16.67 ms |
| 90 Hz | 11.11 ms |
| 120 Hz | 8.33 ms |
但不能简单理解成“App 独占整个周期”。App、RenderThread、GPU 和 SurfaceFlinger 有各自的 deadline,而且是流水执行的。
诊断现代 Android 卡顿时,优先使用:
- Perfetto 的 FrameTimeline;
dumpsys gfxinfo;- Android Studio System Trace;
- Profile HWUI Rendering;
- SurfaceFlinger Layer/transaction trace。
十一、SurfaceView 为什么特殊
普通 View 一般画进 Activity 主窗口对应的 Surface。
SurfaceView 则拥有独立 Surface:
Activity 主 Surface
├── 普通 View 内容
└── SurfaceView 只负责布局/占位等
SurfaceView 独立 Surface
└── 视频、Camera、游戏画面
两个 Surface 在 SurfaceFlinger 中作为不同 Layer 合成。
优点:
- 可以由独立线程持续生产帧;
- 视频解码器、Camera、OpenGL/Vulkan 可以直接输出;
- 有机会使用硬件 overlay,减少额外拷贝和 GPU 合成;
- 不必把视频每帧重新画进 View 树。
但“SurfaceView 不需要 VSync”这个说法不够准确。它可以自主生产和提交 Buffer,不依赖普通 View traversal;但若希望平滑、低延迟、无抖动,生产节奏仍然应该与显示时序协调。
与之对比,TextureView 的内容作为纹理进入 App 窗口的渲染体系,变换更灵活,但往往多一次纹理合成,性能和功耗未必优于 SurfaceView。
十二、最终记忆框架
只需记住下面五句话:
- View 系统决定“画什么、画在哪里”,Surface 提供“把一帧交出去”的通道。
- GraphicBuffer 是帧数据,BufferQueue 是帧数据的传送带,Fence 是异步任务的交接凭证。
- App/RenderThread/GPU 生产单个 Surface 的内容,SurfaceFlinger 管理整个系统的 Layer。
- HWC 在 GPU 合成和显示硬件合成之间分配工作,DPU 最终负责显示侧处理。
- VSync 不负责绘图,它负责给 App、SurfaceFlinger 和显示设备提供统一的时间节拍。
主要链路:
invalidate/requestLayout
→ Choreographer
→ ViewRootImpl traversal
→ DisplayList/RenderNode
→ RenderThread/GPU
→ GraphicBuffer
→ queueBuffer
→ SurfaceFlinger latch
→ HWC composition decision
→ GPU client composition 或 DPU device composition
→ present
→ 屏幕扫描显示
这就是 Android 渲染机制的主干;源码中的绝大多数类和方法,本质上都只是在完成这条链路中的某一次调度、一次内存流转或一次同步。