一块屏幕上可以同时出现应用主界面、状态栏、输入法、相机预览和视频画面。这些画面来自不同内容流,各自通过生产入口和 Buffer 周转通道进入 SurfaceFlinger 的合成场景。
以下以 Android 15 为参考版本,建立从内容生产、Buffer 交接、Layer 状态到最终合成的系统模型,并说明常见窗口路径中的 BLAST 适配层。
系统不会在队列层把所有内容合并成一条“大队列”。每条内容流先独立完成 Buffer 的生产和交接,BLAST(若处在该窗口路径)再把一次提交放进 SurfaceControl Transaction 的语境,SurfaceFlinger 到刷新时才从多个 Layer 的可用状态中选择合成输入。因此,queueBuffer() 成功只说明一次提交被接受,不能直接推出 acquire、latch、合成或扫描已经完成。
一块屏幕不是一条队列
设想一个视频应用正在播放全屏视频,顶部仍显示系统状态栏,底部弹出输入法,应用又叠加了一层播放控件。当前显示结果至少涉及以下内容来源:
| 内容 | 典型生产者 | 典型 Buffer 流 | 合成单元 |
|---|---|---|---|
| 应用主 UI 与播放控件 | HWUI / RenderThread / GPU | 应用主 Surface 对应的周转通道 | 应用主 Layer |
| 视频画面 | MediaCodec | 输出 Surface 对应的周转通道 | SurfaceView 等独立 Layer,或被应用采样进主 UI |
| 状态栏 | SystemUI | SystemUI 自己的 Surface/BufferQueue | 状态栏 Layer |
| 输入法 | IME 进程 | 输入法窗口自己的 Surface/BufferQueue | 输入法 Layer |
这些内容的生产速度、尺寸、格式和生命周期都不同。把它们塞进同一个 Producer/Consumer 契约,会让一条流的背压、重建和缓存变化干扰其他流。Android 更常见的组织方式是让它们分别周转,在合成阶段再统一决定“屏幕上出现什么”。
flowchart LR
UI[App UI / HWUI] --> S1[Surface A]
V[MediaCodec] --> S2[Surface B]
SYS[SystemUI] --> S3[Surface C]
IME[输入法] --> S4[Surface D]
S1 --> Q1[BufferQueue / BLAST A]
S2 --> Q2[BufferQueue B]
S3 --> Q3[BufferQueue / BLAST C]
S4 --> Q4[BufferQueue / BLAST D]
Q1 --> L1[App Layer]
Q2 --> L2[Video Layer]
Q3 --> L3[Status Bar Layer]
Q4 --> L4[IME Layer]
L1 --> SF[SurfaceFlinger Layer 场景]
L2 --> SF
L3 --> SF
L4 --> SF
SF --> COMP[HWC / RenderEngine]
COMP --> DISP[Display]
四条 Buffer 流分别周转;SurfaceFlinger 汇合的是各个 Layer 当前可用的 Buffer 和属性。
参与者分别负责什么
这条链路里最容易混淆的是 Window、Surface、BufferQueue 和 Layer。它们处在不同问题域:
| 对象或角色 | 回答的问题 | 不负责什么 |
|---|---|---|
| Window | 窗口如何被应用与 WindowManager 管理 | 不直接定义 Buffer 池内部状态 |
| Surface / ANativeWindow | Producer 从哪里请求可写 Buffer、向哪里提交 | 不决定 Z 顺序和最终合成策略 |
| BufferQueue | Buffer 如何分配、复用、排队和交接 | 不决定窗口位置、透明度和遮挡 |
| SurfaceControl / Transaction | Layer 的 Buffer 与几何、裁剪、层级等状态如何提交 | 不替代 Producer 对 Buffer 的写入 |
| Layer | SurfaceFlinger 场景中的合成内容单元 | 不等于一个 Java Window,也不保证总有 Buffer |
| SurfaceFlinger | 收集 Layer 状态、组织刷新与合成 | 不替代应用、相机或解码器生产内容 |
| HWC / RenderEngine | 按当前能力完成设备合成或 GPU 客户端合成 | 不改变上游 BufferQueue 的容量策略 |
Surface 是相对于某条内容流的 Producer 入口。Java 层开发者可能通过 Canvas、MediaCodec 或 Camera 间接使用它;native 层通常表现为 Surface / ANativeWindow,内部连接 IGraphicBufferProducer。真正写像素的执行者可以是 CPU、HWUI/RenderThread、EGL/Vulkan、Camera HAL 或 Codec。
Consumer 也不是固定等于 SurfaceFlinger。ImageReader、SurfaceTexture、Codec、BLASTBufferQueue 都可以处在 Consumer 一侧。某个组件还可能一边消费上游 Buffer,一边把处理结果通过另一条 Surface 流继续生产。因此“Producer/Consumer”是相对于一条 BufferQueue 的角色,不是进程或类的永久身份。
多条 BufferQueue 的来源
每调用一次 BufferQueue::createBufferQueue(),都会得到一组新的 Core、Producer 端实现和 Consumer 端实现。系统没有“所有应用共用 N 条 BufferQueue”的固定总数;数量由当前活跃的数据流产生,并随对象生命周期变化。
可能创建独立 Buffer 周转通道的场景包括:
- 应用顶层窗口的主绘制 Surface;
SurfaceView的独立显示 Surface;SurfaceTexture、TextureView使用的输入流;- Camera、MediaCodec、ImageReader、ImageWriter 的输入或输出;
- 录屏、虚拟显示和屏幕采集;
- SystemUI、壁纸、启动画面、输入法等系统界面;
- Android 15 窗口路径中的 BLASTBufferQueue。
单条 BufferQueue 的直接连接语义仍是一名逻辑 Producer 对一名逻辑 Consumer。系统的“多生产者、多消费者”效果主要通过多条队列、转发组件和合成图组织,不是让多个独立窗口同时连接一个 Core。
Window、Layer 与 BufferQueue 不是严格一一对应
“一个普通顶层窗口通常有一条主绘制流”可以作为入门模型,但不能写成全局公式。
一个 Activity Window 可能同时包含:
Activity Window
├── 主 UI Surface / Buffer 流
├── SurfaceView 视频 Surface / Buffer 流
├── Camera → ImageReader 的处理流
└── SurfaceTexture 接收的纹理流
其中主 UI 和 SurfaceView 可能分别形成可见 Layer;ImageReader 的流可能只在应用内部处理;TextureView 的 SurfaceTexture 有自己的 BufferQueue,但内容通常再被采样进应用主 UI Layer。反过来,SurfaceFlinger 中用于容器、颜色或效果的 Layer 也可能没有持续提交像素的 Producer。
因此:
有 BufferQueue
≠ 必然有独立 Window
≠ 必然有独立可见 Layer
有 Layer
≠ 必然有自己的 BufferQueue
窗口重建、Surface 销毁重建、SurfaceView attach/detach、Camera 会话或 Codec 配置变化,还会让这些关系随时间改变。
一帧如何从应用进入 SurfaceFlinger
经典模型常写成“应用向 BufferQueue queue,SurfaceFlinger直接消费”。它适合说明生产—消费关系,但 Android 15 的常见窗口路径还可能经过客户端进程中的 BLASTBufferQueue。
BLASTBufferQueue.cpp 展示了这层适配:BLAST 内部仍创建普通 BufferQueue;本地 Consumer acquire 到 BufferItem 后,再用 SurfaceControl::Transaction::setBuffer() 把 Buffer、Fence、frame number 等状态送入 SurfaceFlinger。
sequenceDiagram
participant App as App / HWUI
participant Surface as Surface
participant BQ as BLAST 内部 BufferQueue
participant BLAST as BLAST Consumer
participant Tx as SurfaceControl Transaction
participant SF as SurfaceFlinger
participant Comp as HWC / RenderEngine
App->>Surface: 请求可写 Buffer
Surface->>BQ: dequeueBuffer()
BQ-->>Surface: slot + GraphicBuffer + release Fence
App->>App: CPU/GPU 写入
Surface->>BQ: queueBuffer(acquire Fence, frame data)
BQ-->>BLAST: onFrameAvailable
BLAST->>BQ: acquireBuffer()
BQ-->>BLAST: BufferItem + GraphicBuffer + Fence
BLAST->>Tx: setBuffer() + 帧属性
Tx->>SF: 提交 Transaction
SF->>SF: 应用 Layer 状态并选择可用 Buffer
SF->>Comp: 组织本次刷新合成输入
Comp-->>SF: present / completion fences
图中只保留从提交到合成的主路径。
Surface 先提供可写目标
应用层看到的是 Surface。HWUI、EGL、Vulkan、CPU Canvas、Camera 或 MediaCodec 通过它取得可写 Buffer。Buffer 的规格由当次请求和 Consumer 约束共同决定,稳定规格下可复用已经分配的图形资源。
这一段只负责生成像素。WindowManager 不写像素,SurfaceFlinger 也不替 Camera 或解码器生产帧。
queueBuffer 只完成一次提交
Producer 写入后调用 queueBuffer(),提交 Slot、裁剪、变换、时间戳、damage 和 acquire Fence 等帧级信息。成功返回能确认 BufferQueue 接受了本次提交,却不能推出:
- GPU 已经写完全部像素;
- Consumer 已经 acquire;
- BLAST 已经把它放入 Transaction;
- SurfaceFlinger 已经选中这帧;
- 这一 Layer 当前可见;
- HWC 或 RenderEngine 已经完成合成;
- 显示设备已经扫描到这帧。
GPU 可以在 queueBuffer() 返回后继续执行,Consumer 通过 acquire Fence 等待真实写入完成。队列状态和硬件完成点是两条相关但不同的时间线。
BLAST 把 Buffer 与 Layer 状态放到同一事务语境
窗口 resize、旋转或转场时,一帧新 Buffer 往往需要配合新的 crop、transform、position 或其他 Layer 属性。若像素提交和几何状态在不同时间生效,可能短暂出现旧帧拉伸、尺寸不匹配或黑边。
BLAST 的职责不是替代 BufferQueue,而是把本地队列产出的 BufferItem 适配为 SurfaceControl Transaction 中的 buffer state。这样,本帧 Buffer、acquire Fence、frame number 和相关帧属性可以更容易与窗口事务协调。
两者职责不同:
BufferQueue:管理 Buffer 池、Slot 状态、提交队列和 Producer/Consumer 交接
BLAST: acquire 一次提交,再把 Buffer 与帧属性装入 SurfaceControl Transaction
SurfaceFlinger 面对的是 Layer 场景
SurfaceFlinger 收到 Transaction 后,把 Buffer 状态与 Layer 的位置、裁剪、透明度、Z 顺序、可见区域等属性放进同一个合成场景。它会在刷新调度中选择适合当前时点的 Layer 状态和 Buffer,而不是每收到一次 queueBuffer() 就立即刷新一次屏幕。
一帧能否进入本次合成,至少受以下条件影响:
| 条件 | 未满足时的结果 |
|---|---|
| Buffer 已到达 SurfaceFlinger 可使用的状态 | 仍停留在上游队列或事务处理中 |
| acquire Fence 已满足,或能作为下游依赖继续传递 | 不能安全读取 Producer 尚未完成的内容 |
| Transaction 已在合适的时点应用 | Buffer 与几何状态可能尚未生效 |
| Layer 可见且处于显示区域 | 即使有新 Buffer 也可能不参与合成 |
| 刷新调度选择了该状态 | 可能等待下一次刷新 |
| 合成和 present 完成 | 屏幕扫描输出仍在更后面 |
这也是排查“queue 成功但没上屏”时需要逐段定位的原因:先确认生产提交,再确认 Consumer/BLAST,接着看 Transaction 和 Layer 可见性,最后看合成与显示完成点。
一个“提交成功但画面没变”的最小推演
把“视频正在播放、输入法刚弹出”当作一个观察场景。视频和输入法各自提交了新内容,但这一刷新周期里屏幕仍可能只显示旧画面。沿同一条证据链看,问题不在“有没有一个全局队列”,而在新状态停在哪一段:
| 观察点 | 发生的状态变化 | 这一步能说明什么 | 还不能说明什么 |
|---|---|---|---|
视频 Producer 调用 queueBuffer() 返回成功 | 视频流的一个 Slot 进入该队列的提交路径 | Producer 已交出一次帧级提交 | Consumer 尚未 acquire,GPU 也可能仍在执行 |
BLAST Consumer 收到并 acquire BufferItem | 本地队列的提交被取出,带上 Buffer、Fence 和帧属性 | 这条流已经越过队列边界 | Transaction 尚未必已在 SurfaceFlinger 生效 |
| SurfaceFlinger 应用 Transaction | 对应 Layer 的 Buffer/几何状态进入场景 | 新状态具备参与刷新选择的条件 | Layer 仍可能不可见或未被本次刷新选中 |
| HWC/RenderEngine 完成本次合成 | 多个 Layer 被组织为本次显示输入 | 这次合成已经完成 | 不能把它倒推为所有上游流都同步完成 |
遇到“提交成功但没有新画面”,按 Producer、队列、BLAST/Transaction、Layer 场景和合成输出逐段定位。以上是 Android 15 硬件加速窗口的常见路径;SurfaceView、TextureView、无 Buffer 的 Layer、软件后端和异常调度需要另行核对。
多路内容在哪里汇合
多条 BufferQueue 的队列、Slot 和背压彼此独立。SurfaceFlinger取得的是各个 Layer 在当前刷新时可用的 Buffer 与状态,再把它们组织成一组合成输入。
flowchart TB
subgraph Streams[独立内容流]
A[App Layer<br/>Buffer A + 属性 A]
B[Video Layer<br/>Buffer B + 属性 B]
C[Status Bar Layer<br/>Buffer C + 属性 C]
end
A --> Scene[SurfaceFlinger<br/>可见 Layer 场景]
B --> Scene
C --> Scene
Scene --> Decide{设备能力与当前 Layer 状态}
Decide -->|适合设备合成| HWC[HWC / Display Composition]
Decide -->|需要客户端合成| RE[RenderEngine / GPU]
RE --> Client[Client Target]
Client --> HWC
HWC --> Display[Display]
HWC 可以让显示硬件直接合成满足条件的 Layer。需要 GPU 客户端合成时,RenderEngine 把相关 Layer 画进 Client Target,再交给 HWC 与其他设备合成 Layer 一起 present。具体选择受设备能力、格式、变换、效果和当帧状态影响,不能从“有几个 BufferQueue”直接推出。
合成阶段可能读取多个输入 Buffer,也可能写入新的合成目标 Buffer。BufferQueue 交接不把整幅像素放进 Binder Parcel,并不代表整条图形管线都不产生额外读写。
Camera、Codec 和应用 UI 如何放回同一张图
相机预览和普通控件通常有两条组织路径:
SurfaceView 路径
Camera 把应用提供的 Surface 当成 Producer 入口,向独立 Buffer 流提交预览帧。SurfaceView 对应的 Layer 与应用主 UI Layer 分开,SurfaceFlinger 在合成阶段组合两者。
Camera/HAL → 预览 Surface → 独立 Buffer 流 → Preview Layer ─┐
App/HWUI → 主 Surface → 主 UI Buffer 流 → App Layer ─────┼→ SurfaceFlinger
SystemUI → 自己的 Surface/流 → System Layer ──┘
Camera 的执行上下文不会变成应用主 UI 的 Producer;应用只是把一条可写 Surface 交给它。
TextureView 路径
Camera 或 Codec 把内容写入 SurfaceTexture 的 BufferQueue。应用渲染时再把该纹理采样进主 UI Buffer,因此可能存在上游媒体流与下游窗口主流两条 BufferQueue,但 SurfaceFlinger 最终只看到合成后的应用主 Layer。
Camera/Codec → SurfaceTexture BufferQueue
↓ 应用采样纹理
App 主 UI 绘制
↓
主 Surface / Buffer 流
↓
App Layer
两种路径都能显示相机或视频,但 Layer 数量、Buffer 流位置和合成边界不同。只数 Window 或只数可见 Layer,都不能准确推导系统中 BufferQueue 的总数。
SurfaceFlinger 的部署边界
标准 Android 系统实例中,SurfaceFlinger 是由 init 启动的独立原生系统服务。固定版本 surfaceflinger.rc 定义主服务,main_surfaceflinger.cpp 创建实例、注册服务并进入运行循环。
通常是一套 Android userspace/Binder 服务域中有一个当前有效的主 SurfaceFlinger 服务,而不是每个 App、Window、Layer 或物理显示各起一个 SurfaceFlinger 进程。一个 SurfaceFlinger 可以管理大量 Layer 和多个显示设备;它内部也不只有一个线程。
因此下面两句话同时成立:
系统中通常只有一个主 SurfaceFlinger 服务角色
系统中同时可以存在很多 BufferQueue 和 Layer
前者描述中心合成服务的部署,后者描述独立内容流和场景节点的数量。
按阶段定位“queue 成功但未上屏”
把一帧从应用跟到屏幕时,可以依次问:
内容由谁生产
→ 写入哪条 Surface / Buffer 流
→ 谁消费这条队列,是否经过 BLAST
→ Buffer 如何进入哪个 Layer
→ 对应 Transaction 是否生效
→ Layer 是否可见、是否被选入本次刷新
→ HWC / RenderEngine 如何完成合成
→ present 和显示完成点是否到达
排查时,将现象对应到一个阶段:没有提交,查 Producer;有提交但没有 acquire,查队列和 Fence;已经进入 Transaction 但没有画面,查 Layer 可见性与刷新选择;已经进入合成仍未显示,查 HWC/RenderEngine 与 present。队列数量本身不能推出帧率、延迟或设备性能。
参考资料
- Android graphics architecture:Surface、BufferQueue、SurfaceFlinger 与 HWC 的整体职责。
- BufferQueue and Gralloc:Buffer 生产、消费与 gralloc 关系。
- SurfaceFlinger and WindowManager:Window、Surface、Layer 与合成边界。
- Synchronization framework:跨 Producer、Consumer 与合成器的显式同步。
BufferQueue.cpp:单条 BufferQueue 的创建入口。Surface.h:Producer 调用端与IGraphicBufferProducer入口。BLASTBufferQueue.cpp:客户端 Consumer 与 Transaction buffer state 的适配。surfaceflinger.rc与main_surfaceflinger.cpp:SurfaceFlinger 服务进程入口。