Android 图形系统横跨应用、系统服务和硬件,组件多、术语杂。本文从应用到硬件梳理一遍:六层分层、层间如何协作、典型场景如何接入,只讲整体,不深入实现细节。
1. 图形系统解决什么问题
屏幕上同时跑着应用界面、视频和系统状态栏,来源不同、节奏各异,最后要合成一帧输出。图形系统要同时处理这几件事:
- 为 Canvas、OpenGL ES、Vulkan、Camera、视频解码器等不同内容源提供统一输出方式。
- 在应用、系统服务和硬件之间共享大块图形内存。
- 同时管理多个独立图像流。
- 将多个窗口、视频和系统 UI 合成为最终画面。
- 输出到物理屏幕、虚拟显示或其他图像消费者。
- 平衡吞吐、延迟、内存、功耗、颜色正确性和内容安全。
2. 从应用到硬件的六层分层
用分层视角建立整体认识:每层解决一类问题,层间通过明确接口衔接。应用开发者最常接触的 View.draw 和 Canvas 位于分层的中上段,是绘制意图的入口;最底层的 GPU、HWC、Display 决定像素如何真正上屏。中间各层把意图一步步转成可上屏的图形数据。
plain
应用侧
1 应用 UI 层 --- View · ViewGroup · Drawable · Window
│ View.draw
▼
2 绘制接口层 --- Canvas · DisplayList/RenderNode · Picture
│ RenderThread 回放
▼
3 渲染引擎层 --- Skia · OpenGL ES · Vulkan
│ 写入 Surface
▼
系统侧
4 缓冲通道层 --- Surface · BufferQueue · GraphicBuffer · Fence
│ 提交 Buffer
▼
5 合成协调层 --- WindowManager · SurfaceControl · SurfaceFlinger · Layer
│ 交付合成
▼
硬件侧
6 硬件显示层 --- Gralloc · GPU · HWC · Display
第 1 层 应用 UI 层
描述界面结构与内容,回答"画什么、怎么布局"。
- View/ViewGroup:界面由 View 树构成,系统对 View 树做 measure(测量)、layout(定位)、draw(绘制)三遍遍历。
- Drawable:可复用的图形资源(位图、矢量、状态选择器等),被 View 持有并绘制。
- Window:应用侧的窗口抽象,一个 Window 通常对应一块可显示区域。
这层不关心像素如何生成,只产出界面要长什么样的描述。
第 2 层 绘制接口层
把 View 的绘制意图表达成一串可回放的绘制操作,与具体渲染后端解耦。硬件加速下,onDraw 里写的 canvas.drawText 不会立刻画到屏幕,而是先记成指令,交给渲染线程稍后执行。
- Canvas:绘制指令接口,提供画几何、文字、图片等操作。两种工作模式:软件模式下直接画进 Bitmap 内存;硬件模式下把指令记录成 DisplayList。
- DisplayList/RenderNode :硬件加速时,
View.draw经 Canvas 把指令记录成 DisplayList(现代实现称 RenderNode),交给 RenderThread 异步回放,避免阻塞主线程。 - Picture:可记录并整体回放的一组绘制指令。
Canvas 的位置有点特殊:既是应用调用的接口,又是渲染引擎的输入。这里把它放在"指令的抽象与记录"这层,执行交给渲染引擎。
第 3 层 渲染引擎层
把绘制指令或图形 API 调用转成像素。前两层只描述画什么,到这层把指令变成像素。
- Skia:跨平台 2D 图形引擎,提供文字、图片、路径等绘制能力,是 Android 硬件加速 2D 渲染(HWUI)使用的渲染引擎。Skia 自身可选不同渲染后端:软件光栅化(CPU),或经 OpenGL ES/Vulkan 调用 GPU。
- OpenGL ES / Vulkan:面向 GPU 的底层图形 API,用于向 GPU 提交绘制任务。游戏和需要高性能图形的应用直接使用它们。
- RenderThread:硬件加速下的渲染线程,回放 DisplayList,调用 Skia→GLES/Vulkan→GPU 生成像素。
这层产出的像素最终写入第 4 层的 Surface。
第 4 层 缓冲通道层
负责图形数据在组件间的传输与同步。
- Surface:生产者提交图像的统一入口;Canvas、GLES、Vulkan、Camera、MediaCodec 都可把结果提交到 Surface。
- ANativeWindow:Surface 背后的原生抽象,是 C/C++ 层接入 BufferQueue 的方式;EGLSurface 则是 GLES 接入 Surface 的封装。
- BufferQueue:生产者/消费者之间的缓冲池与队列,协调 Buffer 的分配、入队、出队。
- GraphicBuffer/HardwareBuffer:图形内存的框架表示,承载一帧或一层图像数据。
- Fence:表达异步硬件任务(如 GPU 渲染、显示读取)何时完成,避免在数据未就绪时读取。
数据流模型:生产者从 BufferQueue 申请 Buffer → 写入内容 → 提交回队列 → 消费者取走使用 → 用完归还。^1^
第 5 层 合成协调层
状态栏、应用窗口、导航栏各自独立绘制,最后要叠成一幅画面。这层管理窗口与图层,把多个图层合成成最终画面。
- WindowManager:系统服务,管理窗口的创建、属性与层级关系。
- SurfaceControl:操作 Layer 的句柄,用于配置图层的位置、尺寸、层级、变换和可见性。
- SurfaceFlinger:系统合成服务,收集所有可见 Layer 的 Buffer,按层级与属性合成。
- Layer:系统合成时处理的图层单位,一个 Surface 对应一个 Layer。
这层的特征是控制流与数据流分离:WindowManager/SurfaceControl 负责配置图层,SurfaceFlinger 负责合成 Buffer。
第 6 层 硬件显示层
提供图形内存、算力与物理输出。前五层的逻辑最终都落到这层的硬件上执行。
- Gralloc:图形内存分配器,为 GPU、显示控制器、Camera 等不同硬件分配可共享的图形内存,是零拷贝传递的关键。
- GPU:执行渲染引擎提交的绘制任务,也可参与合成。
- HWC(Hardware Composer):显示控制器硬件,能高效合成多个 Layer。SurfaceFlinger 把 Layer 交给 HWC,HWC 决定哪些 Layer 由硬件合成、哪些超出能力需回退 GPU 合成。
- Display:物理屏幕或虚拟显示,是最终输出目标。
3. 层级之间的关系
分层建立了纵向认识,这一节用两条主线把它们串起来:数据流(图像怎么从指令变成屏幕像素)和控制流(窗口和图层怎么被配置)。两条线在不同层交汇,由 Producer/Consumer 模型贯穿。
数据流:从绘制指令到屏幕像素
plain
View 树遍历
│ View.draw
▼
Canvas 记录指令 ──► DisplayList/RenderNode
│ RenderThread 回放
▼
Skia / GLES / Vulkan ──► 生成像素
│ 写入
▼
Surface ──► BufferQueue ──► SurfaceFlinger 取 Buffer
│ 合成
▼
HWC / GPU 合成
│
▼
Display 上屏
普通应用窗口沿这条路径完整走一遍:应用侧(1-3 层)生成像素写入 Surface,系统侧(4-6 层)把多个 Surface 的 Buffer 合成上屏。Camera、视频解码等场景作为生产者,可直接从第 3、4 层接入,跳过 1、2 层的 View 体系。^2^
控制流:窗口与图层的配置
数据流之外,还有一条并行的控制流:
plain
WindowManager / SurfaceControl
│ 配置 Layer 属性(位置、层级、变换、可见性)
▼
SurfaceFlinger ──► 按配置合成可见 Layer ──► HWC/Display
Window 是第 1 层应用侧的窗口抽象,它的实际管理发生在第 5 层:WindowManager 通过 SurfaceControl 创建并配置 Layer,SurfaceFlinger 依据这些配置决定合成顺序与效果。界面变化时,控制流(窗口/图层属性更新)与数据流(重新绘制)协同工作。
贯穿全程的 Producer/Consumer 模型
第 3-5 层由一个统一模型贯穿:
- 生产者:渲染引擎(Skia/GLES/Vulkan)、Camera、MediaCodec 等,产生图形 Buffer。
- 消费者:SurfaceFlinger(合成上屏)、SurfaceTexture(转纹理继续处理)、ImageReader(图像分析)、MediaCodec Encoder(编码)等。
- 中介:BufferQueue 连接两端,Buffer 在其中流转。
同一个生产者可对接不同消费者:Camera 的 Buffer 既能给 SurfaceFlinger 上屏预览,也能给 ImageReader 做分析,还能给编码器录像,区别只在于 BufferQueue 连接哪两端。
分层带来的解耦
每层都解决一类解耦问题:
- 应用与硬件解耦:应用只画到 Surface,无需感知 GPU 型号或屏幕硬件。
- 指令与执行解耦:DisplayList 把绘制意图与渲染时机分离,主线程记录、RenderThread 异步执行。
- 生产与消费解耦:BufferQueue 让生产者和消费者速度不同也能协同,多重缓冲提升吞吐。
- 内存共享解耦:Gralloc 分配的 Buffer 在 GPU、HWC、CPU 间零拷贝传递,Fence 保证异步读写安全。
- 后端可替换:Skia 可在软件、GLES、Vulkan 后端间切换,上层接口不变。
4. 典型图形场景
用分层视角看不同场景,区别在于从哪一层接入、消费者是谁:
| 场景 | 生产者 | 消费者/输出 |
|---|---|---|
| 普通应用窗口 | HWUI/Skia | SurfaceFlinger |
| Canvas 直接绘制 | CPU/Skia Canvas | SurfaceFlinger |
| OpenGL ES 游戏 | GLES/EGL | SurfaceFlinger |
| Vulkan 游戏 | Vulkan Swapchain | SurfaceFlinger |
| Camera 预览 | Camera HAL | SurfaceFlinger 或 SurfaceTexture |
| 视频播放 | MediaCodec Decoder | SurfaceFlinger 或 SurfaceTexture |
| 视频编码 | Camera/GLES | MediaCodec Encoder |
| 图像分析 | Camera/Codec | ImageReader |
| 屏幕录制 | SurfaceFlinger/虚拟显示 | MediaCodec Encoder |
每个场景都要弄清:生产者是谁、写到哪个 Surface、BufferQueue 连接哪两端、谁最终消费、是否合成上屏。SurfaceView、SurfaceTexture、TextureView 是不同场景的接入方案,而非单独的架构层。^2^
5. 常见疑问
多重缓冲的作用 单缓冲下,生产者画完一帧必须等显示读完才能画下一帧,期间 GPU/CPU 空闲。多缓冲让生产者画到后台 buffer 的同时、显示读取前台 buffer,形成流水线,提升吞吐、降低卡顿。三缓冲更进一步:即使一帧没渲染完,生产者仍总有 buffer 可写。
BufferQueue 如何应对生产与消费速度不匹配 队列在两端之间蓄水:生产快时 buffer 堆积到上限会阻塞生产者或丢帧;生产慢时消费者复用上一帧。多重缓冲让速度不匹配的两端按各自节奏运转,互不阻塞。
为什么避免直接复制整帧像素 一帧数据量很大(1080p RGBA 约 8MB),复制既耗时又耗带宽和功耗。Android 用 Gralloc 分配可被 GPU、CPU、HWC 共享访问的图形内存,组件间传递的是内存句柄而非数据本身,零拷贝完成流转。
HWC 与 GPU 在性能和功耗上的取舍 HWC 是显示控制器硬件,合成多 layer 比用 GPU 省电:专用硬件、不占 GPU,还能让 GPU 休眠。SurfaceFlinger 优先用 HWC 合成,只有超出 HWC 能力(复杂变换、layer 过多、特殊混合模式)才回退 GPU,此时既耗电又占用应用渲染资源。
Fence 的作用 图形任务(GPU 渲染、显示读取、Camera 输出)是异步硬件操作,CPU 提交后无法同步等待完成。Fence 是内核同步原语,标记"这块 buffer 何时写完或读完"。没有它,消费者可能读到半成品,或生产者覆盖还在被读的 buffer。它用信号替代忙等,保证异步硬件链路的正确顺序。
SurfaceView 与 TextureView 的行为差异 SurfaceView 拥有独立 Surface,直接作为独立 layer 交给 SurfaceFlinger 合成,不参与 View 体系。性能好,但变换和透明受限,还可能和 View 层叠时出现缝隙。 TextureView 把内容当作纹理,先写入 Buffer,再作为纹理合成进 View 的绘制结果,完全纳入 View 层级,支持任意变换与透明,代价是多一次纹理合成。 两者本质区别:独立 layer 合成,还是作为纹理纳入 View 层级。
部分视频无法截图的原因 受 DRM 保护的内容(如 Widevine L1)在安全硬件中解码,输出到受保护的 buffer,标记为 protected,不允许 GPU 或 CPU 读取。截图要读 framebuffer,SurfaceFlinger 对这类 layer 标记 secure,截图时跳过,这是内容安全机制。
相同分辨率下 Buffer 占用的差异来源 取决于像素格式和分配方式。同分辨率下,RGBA_8888(每像素 4 字节)是 RGB_565(每像素 2 字节)的两倍;是否启用帧缓冲压缩(如 AFBC)、不同 usage 标志导致的对齐与布局差异,都会改变实际占用。
物理显示与虚拟显示的区别 物理 Display 接真实屏幕硬件,经 HWC 输出,直接给用户看。 虚拟 Display 没有真实硬件,把合成结果输出到一个可被应用消费的 Surface,用于屏幕录制、投屏等。合成结果不直接上屏,而是作为 buffer 送出。
WindowManager 与 SurfaceFlinger 的分工 WindowManager 管窗口的语义和策略(创建、属性、层级、焦点),SurfaceFlinger 管像素层面的合成(收集 buffer、合成、输出)。分开后 WM 不需懂合成,SF 不需懂窗口策略;SF 跑在独立进程,合成实时性强,不能被窗口管理逻辑阻塞。两者通过 SurfaceControl 桥接:WM 配置 layer,SF 执行合成。
小结
从 View.draw 到屏幕像素,要穿过应用 UI、绘制接口、渲染引擎、缓冲通道、合成协调、硬件显示六层,由数据流和控制流两条主线贯穿。带着这个框架去看 BufferQueue、SurfaceFlinger 或 HWC 的专题,每块都能对应到具体的层和主线。