一块屏幕上可以同时出现应用主界面、状态栏、输入法、相机预览和视频画面。这些画面来自不同内容流,各自通过生产入口和 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 更常见的组织方式是让它们分别周转,在合成阶段再统一决定"屏幕上出现什么"。
四条 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 可能同时包含:
text
Activity Window
├── 主 UI Surface / Buffer 流
├── SurfaceView 视频 Surface / Buffer 流
├── Camera → ImageReader 的处理流
└── SurfaceTexture 接收的纹理流
其中主 UI 和 SurfaceView 可能分别形成可见 Layer;ImageReader 的流可能只在应用内部处理;TextureView 的 SurfaceTexture 有自己的 BufferQueue,但内容通常再被采样进应用主 UI Layer。反过来,SurfaceFlinger 中用于容器、颜色或效果的 Layer 也可能没有持续提交像素的 Producer。
因此:
text
有 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。
图中只保留从提交到合成的主路径。
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 和相关帧属性可以更容易与窗口事务协调。
两者职责不同:
text
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 与状态,再把它们组织成一组合成输入。
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 在合成阶段组合两者。
text
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。
text
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 和多个显示设备;它内部也不只有一个线程。
因此下面两句话同时成立:
text
系统中通常只有一个主 SurfaceFlinger 服务角色
系统中同时可以存在很多 BufferQueue 和 Layer
前者描述中心合成服务的部署,后者描述独立内容流和场景节点的数量。
按阶段定位"queue 成功但未上屏"
把一帧从应用跟到屏幕时,可以依次问:
text
内容由谁生产
→ 写入哪条 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 服务进程入口。