Appearance
SurfaceFlinger、BufferQueue 与三重缓冲:帧如何合成上屏
回到总览:内存、生命周期、系统约束相关模块:Android View 绘制流程、Choreographer 与 VSync 帧同步 · Flutter 三棵树与 Layer Tree:Widget / Element / RenderObject / Layer
一句话定义
应用把一帧画进 BufferQueue 的图形缓冲(生产者),SurfaceFlinger(系统合成服务)作为消费者把各 Surface 的缓冲通过 HWC(Hardware Composer) 合成到屏幕帧缓冲;为避免因「应用占一块、合成占一块、无空闲块」而丢帧,Android 用 三重缓冲(3 个缓冲轮换)——它是 09 页 Choreographer 之后、「帧最终上屏」的那一环。
代码索引
对应 Lab:surfaceflinger-bufferqueue ——
dumpsys SurfaceFlinger观察 BufferQueue 与 triple buffering(adb 观测型)
为什么需要
- 为什么单/双缓冲会出现「撕裂」或「应用一卡整屏掉帧」?
- 一句话答:单缓冲时 GPU 正在写、屏幕正在读同一块,会撕裂;双缓冲只有「前缓冲(显示中)+ 后缓冲(绘制中)」,若某一帧应用没在 VSync 内画完,两块都被占、屏幕只能重复上一帧 → 掉帧。三重缓冲多给一块空闲缓冲吸收抖动(详见 §底层机制.3)。
- 为什么应用「画完了」却还不是立刻上屏?
- 一句话答:应用只是把一个 Buffer 入队给 BufferQueue,真正上屏由 SurfaceFlinger 在下一个 VSync 按合成策略(HWC Overlay 或 GPU 合成)决定,两者异步;这也解释了为什么掉帧归因要分「应用没画完」和「合成卡住」两段。
- 为什么过度重叠的视图会让 SurfaceFlinger 变重、整机掉帧?
- 一句话答:每个 Surface/图层都要被 SF 合成,图层越多、越不能走 HWC 的硬件 Overlay(只能 GPU 合成)时,SF 主线程与 GPU 负载上升,成为新的瓶颈,表现为「应用本身不慢但整机卡」。
底层机制
1. 生产者—消费者:BufferQueue
应用(通过 HWUI/GL/Vulkan 绘制)是 BufferQueue 的生产者,SurfaceFlinger 是消费者;一块缓冲在「应用绘制 → 入队 → SF 合成 → 出队归还」间循环。
- 预期现象:应用
dequeueBuffer拿一块空缓冲作画,queueBuffer交还;SFacquireBuffer取最新一帧合成后releaseBuffer归还池子,缓冲循环复用。
2. SurfaceFlinger 合成与 HWC
SF 收集所有 Surface 的待合成缓冲,交给 HWC:能直接由显示硬件叠加的走 Overlay(几乎零成本),不能的走 GPU 合成(Client 合成,有开销)。
- 可能输出(
dumpsys SurfaceFlinger片段示意)HWC layers: 2 (overlay) GPU composition: 1 layer (client) - 预期现象:图层越少、越能走 Overlay,合成越便宜;频繁出现「GPU composition」且层数多,就是 SF 过载信号。
3. 三重缓冲为何能吸收抖动
双缓冲只有两块:若应用这帧超时,两块都被占用 → 下一 VSync 无可显示新帧 → 掉帧。三缓冲多一块空闲,允许应用「提前画下一帧」以吸收偶发超时。
- 预期现象:三重缓冲用「多一点内存/延迟」换「更少掉帧」;但缓冲越多延迟越大,Android 默认 3 块是体验与延迟的折中。
- 代价:缓冲越多,端到端延迟(触摸→显示)越大,竞技类场景需权衡。
Android / Flutter / iOS / Web 对照
| 维度 | Android | Flutter | iOS(Core Animation) | Web |
|---|---|---|---|---|
| 合成服务 | SurfaceFlinger | Engine→Surface→SF | Render Server(CARenderServer) | Compositor |
| 缓冲模型 | BufferQueue + 三重缓冲 | Layer→Surface | CALayer 树→合成 | 合成层(will-change) |
| 硬件合成 | HWC Overlay | 经 SF/HWC | Metal/Core Animation 硬件层 | GPU 合成层 |
| 掉帧归因 | 应用超 VSync / SF 过载 | UI/Raster TaskRunner 超时 | 主线程/渲染服务阻塞 | 主线程长任务/Layout Thrash |
常见场景
- 滚动手势掉帧:应用一帧绘制超过 16.6ms(60Hz),BufferQueue 无空闲块吸收 → 可见掉帧;用 Perfetto 看是「应用绘制慢」还是「SF 合成慢」。
- 多 Surface 叠加卡顿:相机预览 + UI + 弹窗多层 Surface,部分只能 GPU 合成,SF 负载高;优先减少不必要的 Surface、让更多层走 Overlay。
- 高延迟敏感场景:游戏/手写笔把缓冲数压到最低(或关部分 triple buffering 效果)以降低笔触延迟,牺牲少量抗抖动。
常见误配、事故后果与排障
1. 误配:滥用独立 Surface / 过度绘制,SF 被迫全 GPU 合成
- 现象:明明图层简单却大量
GPU composition,整机掉帧、发热。 - 排障与修法:减少不必要的 Surface(如不必要的
SurfaceView/独立窗口),扁平化重叠背景(同 09 页过度绘制治理),让 HWC 能走 Overlay;用dumpsys SurfaceFlinger核对 Overlay/GPU 比例。
2. 事故:应用长期不按 VSync 节奏提交,BufferQueue 堆积
- 后果:应用侧「画完了但晚到」,SF 拿不到新帧只能重复旧帧,表现为周期性掉帧。
- 排障与修法:回到 09 页——用 Choreographer 驱动绘制,确保每帧在 VSync 窗口内完成;主线程长任务(锁/IO)是常见根因。
3. 误配:为了「低延迟」盲目砍缓冲,反而更易掉帧
- 现象:把三重缓冲相关参数压到最低,偶发抖动直接变成可见掉帧,体验更差。
- 排障与修法:缓冲数是延迟/稳定的折中;除非是高精度输入场景,否则保留默认三重缓冲,先治「应用超时才是对的」。
相近概念对比
| 概念 | 它是什么 | 易混点 |
|---|---|---|
| BufferQueue | 应用↔SF 的缓冲生产者消费者队列 | 不是「双缓冲」本身,是承载缓冲的队列 |
| 双缓冲 | 前+后两块 | 抖动时易掉帧,缺空闲块 |
| 三重缓冲 | 多一块空闲吸收抖动 | 换来更低掉帧,但更高延迟 |
| HWC Overlay | 显示硬件直叠,零成本 | 不是所有图层都能走,受硬件层数量限制 |
| GPU 合成 | 不能 Overlay 时回退 | 有开销,是 SF 过载信号 |
对应实验
| 实验 | 说明 | 关键源码 |
|---|---|---|
| surfaceflinger-bufferqueue | dumpsys SurfaceFlinger 观察 BufferQueue 与 triple buffering、Overlay/GPU 合成比例 | adb 观测,无源码 |
复习检查题
为什么三重缓冲能减少掉帧,而双缓冲在「应用偶发卡一下」时更容易掉帧? 答:双缓冲只有「显示中 + 绘制中」两块;若应用某一帧没在 VSync 内画完,两块都被占用,下一 VSync 没有可显示的新帧,只能重复旧帧即掉帧。三重缓冲多一块空闲缓冲,允许应用在超时帧期间「提前画下一帧」,把偶发抖动吸收掉——用稍多的内存与延迟换取更少掉帧。
应用「画完了」为什么不等于「立刻上屏」?掉帧归因为什么要分两段? 答:应用只是把画好的 Buffer 入队给 BufferQueue,真正上屏由 SurfaceFlinger 在下一个 VSync 按合成策略决定;两者异步。所以掉帧可能是「应用没在窗口内画完」(生产慢),也可能是「SF 合成卡住 / 图层太多被迫 GPU 合成」(消费慢),归因必须分「生产者超时」和「消费者过载」两段,治法不同。
为什么过度重叠的视图会让整机掉帧,而不是只影响那个 App? 答:每个 Surface/图层都要 SF 合成,超出 HWC 能硬件 Overlay 的数量后只能走 GPU 合成,SF 主线程与 GPU 负载上升,成为系统级瓶颈;表现就是「单个 App 本身绘制不慢,但整机卡、发热」。治理靠减少 Surface 与重叠、让更多层走 Overlay(配合 09 页的过度绘制治理)。
速记
- BufferQueue:应用生产、SF 消费,缓冲循环复用。
- SF 合成:能 Overlay 就硬件直叠(零成本),不能才 GPU 合成(开销)。
- 三重缓冲:多一块空闲吸抖动,代价是更高延迟。
- 掉帧两段:应用超 VSync(生产慢) / SF 过载(消费慢)。