Android 图形显示系统:Canvas、Skia、Surface与SurfaceFlinger
Android 图形系统:Canvas、Skia、Surface 与 SurfaceFlinger
上一篇介绍了 Activity、Window、DecorView 和 ViewRootImpl 的关系。
当 ViewRootImpl 发起 draw() 后,问题就从“界面如何组织”进入了“图像如何生成”:
- View 为什么需要 Canvas?
- Canvas 是不是直接操作屏幕?
- Skia、OpenGL ES 和 Vulkan 是什么关系?
- Surface 到底保存了什么?
- WindowManagerService 和 SurfaceFlinger 为什么缺一不可?
- SurfaceView 为什么能让视频解码器直接输出画面?
本文从一次 View 绘制开始,把 Android 图形系统的主链路串起来。
一、先看完整渲染链路
flowchart TD
A[ViewRootImpl 发起 draw] --> B[View 树]
B --> C[Canvas 绘图接口]
C --> D{绘制模式}
D -->|软件绘制| E[Skia Raster]
E -->|CPU 光栅化| J[Surface Buffer]
D -->|硬件加速| F[RenderNode / DisplayList]
F --> G[HWUI / RenderThread]
G --> H[Skia GPU]
H --> I[OpenGL ES 或 Vulkan]
I --> J
J --> K[BufferQueue]
K --> L[SurfaceFlinger]
M[WindowManagerService] -->|窗口位置、层级和裁剪信息| L
L --> N[GPU 或 Hardware Composer 合成]
N --> O[屏幕]
可以先记住:
View 通过 Canvas 描述要画什么,Skia 和图形后端生成像素,Surface 提交图形 Buffer,SurfaceFlinger 合成所有图层并输出到屏幕。
二、Canvas:描述“画什么”的绘图接口
自定义 View 通常会重写 onDraw():
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(
width / 2f,
height / 2f,
100f,
paint
)
}
Canvas 提供了常见绘图能力:
- 绘制文字;
- 绘制矩形、圆形和路径;
- 绘制 Bitmap;
- 裁剪绘制区域;
- 平移、旋转和缩放坐标系;
- 设置图层和混合效果。
Canvas 的职责是提供统一的二维绘图 API。
它描述的是:
需要绘制什么图形,以及用什么坐标和参数绘制。
Canvas 本身不是屏幕,也不是固定的 GPU API。
同样的 Canvas API,背后可能连接:
- CPU 软件光栅化;
- GPU 硬件加速;
- Bitmap;
- Surface;
- RenderNode 记录过程。
因此不能简单理解为:
flowchart LR
A[Canvas] --> B[直接修改屏幕像素]
正确关系是:
flowchart LR
A[View] --> B[Canvas API]
B --> C[渲染实现]
C --> D[图形 Buffer]
三、Skia:Android 二维图形引擎
Skia 是一个开源二维图形库,也是 Android 图形系统的重要基础组件。
它主要处理:
- 路径和几何图形;
- 字体和文字排版;
- 图片绘制;
- 颜色和渐变;
- Shader;
- 混合模式;
- 抗锯齿;
- 图像滤镜;
- 光栅化。
Canvas 解决的是“用什么 API 描述绘制”,Skia 解决的是“怎样把这些二维图形转换成可显示结果”。
flowchart LR
A[Canvas 绘图命令] --> B[Skia]
B --> C[计算路径、文字、颜色和混合]
C --> D[选择 CPU 或 GPU 后端]
Skia 并不等于 OpenGL ES。
Skia 可以使用不同后端:
flowchart TD
A[Skia 绘图任务] --> B{选择渲染后端}
B --> C[Raster CPU]
B --> D[OpenGL GPU]
B --> E[Vulkan GPU]
因此,“View 渲染一定经过 Canvas、Skia、OpenGL ES”只说对了一部分。
更准确的结论是:
View 通常通过 Canvas 描述绘制,由 Skia 参与二维图形处理;最终可能使用 CPU、OpenGL ES 或 Vulkan 完成光栅化。
四、软件绘制:CPU 直接生成像素
关闭硬件加速,或者某一部分使用软件图层时,会走软件绘制路径。
flowchart TD
A[ViewRootImpl] --> B[View.draw]
B --> C[View.onDraw]
C --> D[Software Canvas]
D --> E[Skia Raster]
E -->|CPU 计算像素| F[内存 Buffer]
F --> G[通过 Surface 提交]
这条链路的主要特点是:
- View 的 Canvas 操作由 CPU 执行;
- Skia 的 Raster 后端负责光栅化;
- 结果直接写入 CPU 可访问的内存 Buffer;
- 不要求经过 OpenGL ES;
- 复杂页面可能给 UI 线程和 CPU 带来较大压力。
软件绘制可以简化为:
flowchart LR
A[Canvas] --> B[Skia Raster]
B --> C[CPU]
C --> D[Surface Buffer]
五、硬件加速:先记录,再由渲染线程执行
现代 Android 应用一般默认开启硬件加速。
硬件加速时,View.onDraw(canvas) 仍然会执行,但 Canvas 调用通常不会立即逐像素绘制。
它们首先被记录成可复用的绘制指令。
flowchart TD
A[View.onDraw] --> B[Hardware Canvas]
B --> C[记录 drawRect drawText 等操作]
C --> D[RenderNode / DisplayList]
D --> E[HWUI]
E --> F[RenderThread]
F --> G[Skia GPU]
G --> H[OpenGL ES 或 Vulkan]
H --> I[GPU]
I --> J[Surface Buffer]
这套设计分成两个主要阶段。
阶段一:UI 线程记录绘制命令
flowchart LR
A[View.onDraw] --> B[Canvas.drawXXX]
B --> C[RecordingCanvas]
C --> D[RenderNode]
例如:
canvas.drawRect(...)
canvas.drawText(...)
canvas.drawBitmap(...)
这些调用更像是在生成一份绘图指令,而不是每次调用都立即写出最终像素。
阶段二:RenderThread 执行渲染
flowchart LR
A[RenderNode / DisplayList] --> B[HWUI]
B --> C[RenderThread]
C --> D[Skia]
D --> E[OpenGL ES / Vulkan]
E --> F[GPU]
F --> G[Surface Buffer]
这样做的好处包括:
- 未发生变化的 RenderNode 可以复用;
- 一部分渲染工作可以从 UI 线程移出;
- 动画属性可以更高效地更新;
- GPU 更适合并行执行大量像素和几何计算。
不过,硬件加速不代表 CPU 完全不参与。
View 遍历、绘图命令记录、文字处理、路径准备、资源上传和驱动调用等环节仍可能使用 CPU。
六、OpenGL ES 和 Vulkan 在什么位置?
OpenGL ES 和 Vulkan 都属于较底层的 GPU 图形 API。
它们主要负责:
- 管理纹理和缓冲区;
- 创建和执行 Shader;
- 向 GPU 提交绘制命令;
- 管理同步;
- 把渲染结果写入目标 Buffer。
Skia 与 OpenGL ES、Vulkan 的关系可以理解为:
flowchart TD
A[Skia] --> B[理解二维图形语义]
B --> C[生成底层渲染任务]
C --> D{GPU API}
D --> E[OpenGL ES]
D --> F[Vulkan]
E --> G[GPU 驱动]
F --> G
Skia 更关注“文字、路径、图片和颜色应该如何绘制”,OpenGL ES 或 Vulkan 更关注“怎样把任务提交给 GPU”。
因此它们不是并列替代关系,而是不同抽象层次上的组件。
七、Surface:图形 Buffer 的生产端接口
Surface 经常被误解成“屏幕上的一块区域”。
更准确地说:
Surface 是应用向 BufferQueue 申请、写入并提交图形 Buffer 的生产端接口。
应用不会直接修改物理屏幕。
绘制结果需要先进入 Buffer:
flowchart TD
A[CPU 或 GPU 生成一帧] --> B[Surface]
B --> C[获取可写 Buffer]
C --> D[完成绘制]
D --> E[提交 Buffer]
E --> F[BufferQueue]
Surface 自己并不是一张永久固定的 Bitmap。
它背后关联的是一套 BufferQueue 和多个可轮换使用的图形 Buffer。
八、BufferQueue:连接生产者和消费者
Android 图形数据大量依赖 BufferQueue 传递。
BufferQueue 一端是生产者,另一端是消费者。
flowchart LR
A[生产者] -->|dequeueBuffer| B[BufferQueue]
A -->|绘制完成后 queueBuffer| B
B -->|acquireBuffer| C[消费者]
C -->|使用完成后 releaseBuffer| B
典型生产者包括:
- Android UI 渲染系统;
- OpenGL ES;
- Vulkan;
- Camera;
- MediaCodec 视频解码器。
典型消费者包括:
- SurfaceFlinger;
- SurfaceTexture;
- ImageReader;
- 视频编码器。
普通 Activity 主窗口中,应用渲染系统通常是生产者,SurfaceFlinger 是消费者。
flowchart LR
A[应用 UI 渲染] --> B[Surface / BufferQueue]
B --> C[SurfaceFlinger]
BufferQueue 的价值是:
- 生产和消费可以异步进行;
- 避免生产者直接修改正在显示的 Buffer;
- 支持多缓冲;
- 通过 Fence 处理 GPU 和显示系统之间的同步;
- 减少不必要的数据复制。
九、Surface 与 ViewRootImpl 的关系
ViewRootImpl 负责驱动 View 树,Surface 负责承接渲染结果。
flowchart LR
A[ViewRootImpl] -->|发起 measure layout draw| B[View 树]
B --> C[Canvas / RenderNode]
C --> D[Skia / HWUI]
D -->|写入| E[Surface Buffer]
ViewRootImpl 本身不是二维图形引擎,也不是 Buffer。
它的职责是:
- 管理根 View;
- 调度 View 遍历;
- 管理窗口尺寸和 Insets;
- 与窗口系统通信;
- 协调窗口使用的 Surface 相关资源;
- 在 Surface 有效时发起绘制。
可以概括为:
ViewRootImpl 决定什么时候画,View 和 Canvas 描述画什么,Skia 与图形后端负责怎么画,Surface 决定画面提交到哪里。
十、WindowManagerService:管理窗口关系
WindowManagerService 不负责逐像素绘制 View。
它主要管理:
- 窗口位置;
- 窗口尺寸;
- 窗口层级;
- 窗口可见性;
- 窗口焦点;
- 屏幕旋转;
- 分屏和画中画;
- 窗口权限;
- 窗口裁剪和 Insets;
- 窗口转场。
flowchart TD
A[WindowManagerService] --> B[窗口位置]
A --> C[窗口大小]
A --> D[窗口层级]
A --> E[窗口焦点]
A --> F[窗口可见性]
WMS 解决的是:
每个窗口应该怎样存在于系统中。
十一、SurfaceFlinger:合成所有可见图层
每个应用只会生成自己的窗口内容,但屏幕上还可能有:
- 状态栏;
- 导航栏;
- 输入法;
- 其他应用窗口;
- Dialog;
- SurfaceView;
- 画中画窗口。
这些内容最终由 SurfaceFlinger 统一合成。
flowchart TD
A[应用主窗口 Buffer] --> E[SurfaceFlinger]
B[状态栏 Buffer] --> E
C[输入法 Buffer] --> E
D[SurfaceView Buffer] --> E
F[WindowManagerService] -->|位置、层级、裁剪和变换信息| E
E --> G[选择合成策略]
G --> H[GPU RenderEngine 合成]
G --> I[Hardware Composer 合成]
H --> J[显示设备]
I --> J
SurfaceFlinger 负责:
- 获取各个可见图层的新 Buffer;
- 根据窗口元数据设置图层位置和层级;
- 处理裁剪、旋转、缩放和透明度;
- 选择 GPU 或 Hardware Composer 进行合成;
- 把最终画面发送到显示设备。
需要强调:
SurfaceFlinger 不理解 Button、TextView 或 RecyclerView,它只处理图层、Buffer 和显示属性。
十二、WMS 与 SurfaceFlinger 为什么都需要?
WMS 与 SurfaceFlinger 分别解决不同问题。
| 组件 | 负责的问题 |
|---|---|
| WindowManagerService | 窗口应该放在哪里、显示多大、位于哪一层、是否可见、谁有焦点 |
| SurfaceFlinger | 获取哪些图层 Buffer,并怎样把它们合成为最终画面 |
二者的协作关系是:
flowchart LR
A[WindowManagerService] -->|窗口元数据| B[SurfaceFlinger]
C[各个 Surface] -->|图形 Buffer| B
B --> D[最终合成画面]
D --> E[屏幕]
可以用一句话区分:
WMS 管窗口秩序,SurfaceFlinger 管图层合成。
十三、SurfaceView 为什么拥有独立 Surface?
普通 View 通常共享 Activity 主窗口的 Surface。
flowchart TD
A[Activity 主 Surface]
B[TextView] --> A
C[ImageView] --> A
D[RecyclerView] --> A
E[普通自定义 View] --> A
而 SurfaceView 会创建一条独立的 Surface 和 BufferQueue 链路。
flowchart TD
A[Activity Window] --> B[主窗口 Surface]
A --> C[SurfaceView]
C --> D[独立 Surface]
D --> E[独立 SurfaceFlinger Layer]
B --> F[SurfaceFlinger]
E --> F
F --> G[屏幕]
这让 SurfaceView 适合:
- 视频播放;
- Camera 预览;
- 游戏画面;
- 高频图像输出;
- MediaCodec 直接输出。
例如视频解码器可以直接把画面写入 Surface:
flowchart LR
A[MediaCodec] --> B[SurfaceView Surface]
B --> C[BufferQueue]
C --> D[SurfaceFlinger]
D --> E[屏幕]
不需要先将每一帧转换成 Bitmap,再交给普通 View 绘制。
十四、TextureView 与 SurfaceView 的差别
TextureView 内部使用 SurfaceTexture 接收 Buffer,但它会把最新 Buffer 作为纹理参与普通 View 树绘制。
flowchart TD
A[视频或相机生产者] --> B[SurfaceTexture BufferQueue]
B --> C[TextureView 获取最新纹理]
C --> D[参与普通 View 树渲染]
D --> E[Activity 主 Surface]
SurfaceView 通常拥有独立图层:
flowchart TD
A[视频或相机生产者] --> B[SurfaceView 独立 Surface]
B --> C[独立 SurfaceFlinger Layer]
简单比较:
| 类型 | 图像如何进入屏幕 | 主要特点 |
|---|---|---|
| 普通 View | 绘制到 Activity 主 Surface | 适合普通 UI |
| TextureView | 作为纹理参与主 View 树绘制 | 容易执行旋转、缩放、透明度等 View 变换 |
| SurfaceView | 独立 Surface 和独立图层 | 适合视频、相机和高频画面 |
SurfaceView 的独立图层通常效率更高,但它与普通 View 的同步、动画和叠加关系也更复杂。
十五、一帧是怎样显示出来的?
把 View 绘制与图层合成放到一帧中,可以得到下面的主链路:
sequenceDiagram
participant VS as VSync
participant C as Choreographer
participant VRI as ViewRootImpl
participant View as View 树
participant RT as RenderThread
participant Skia
participant Surface
participant SF as SurfaceFlinger
participant HWC as Hardware Composer
participant Display as 屏幕
VS->>C: 帧同步信号
C->>VRI: 执行当前帧回调
VRI->>View: measure / layout / draw
View->>RT: 提交 RenderNode 绘制内容
RT->>Skia: 执行二维图形渲染
Skia->>Surface: 写入并提交 Buffer
Surface->>SF: 新 Buffer 可用
SF->>HWC: 提交图层合成任务
HWC->>Display: 输出最终画面
实际系统还会涉及:
- FrameTimeline;
- Sync Fence;
- BLASTBufferQueue;
- SurfaceControl;
- RenderEngine;
- EGL;
- GPU 驱动;
- HWC HAL。
但这些属于更深入的图形系统实现,不影响本文的主链路。
十六、常见误区
误区一:Canvas 就是 Skia
不准确。
Canvas 是绘图接口,Skia 是处理和执行二维图形任务的底层引擎之一。
误区二:Canvas 会直接画到屏幕
不准确。
Canvas 的结果还要经过光栅化、Surface Buffer 和 SurfaceFlinger 合成。
误区三:硬件加速后不再使用 Canvas
不准确。
View 仍通过 Canvas API 描述绘制,只是硬件 Canvas 通常会先记录绘制命令。
误区四:View 绘制一定经过 OpenGL ES
不准确。
软件绘制可以使用 CPU Raster,硬件绘制也可能使用 Vulkan。
误区五:Surface 是一块固定内存
不准确。
Surface 是 BufferQueue 生产端接口,背后通常有多块轮换使用的图形 Buffer。
误区六:SurfaceFlinger 负责绘制 View
不准确。
SurfaceFlinger 负责合成图层,不负责执行 TextView 或自定义 View 的 onDraw()。
误区七:WMS 与 SurfaceFlinger 是同一个服务
不准确。
WMS 管窗口关系,SurfaceFlinger 管图形 Buffer 和图层合成。
十七、各组件职责总结
| 组件 | 核心职责 |
|---|---|
| View | 定义页面内容和绘制逻辑 |
| Canvas | 提供二维绘图命令 |
| RenderNode / DisplayList | 记录并复用硬件加速绘制操作 |
| HWUI | Android View 硬件加速渲染框架 |
| RenderThread | 执行应用 UI 渲染任务 |
| Skia | 处理文字、路径、图片和二维光栅化 |
| OpenGL ES / Vulkan | 将底层图形任务提交给 GPU |
| Surface | BufferQueue 的生产端接口 |
| BufferQueue | 在图形生产者与消费者之间传递 Buffer |
| WindowManagerService | 管理窗口位置、层级、焦点和权限 |
| SurfaceFlinger | 合成全部可见图层 |
| Hardware Composer | 使用显示硬件协助图层合成 |
十八、最终架构图
flowchart TD
subgraph App[应用进程]
A[ViewRootImpl]
B[View 树]
C[Canvas]
D[RenderNode / DisplayList]
E[HWUI / RenderThread]
F[Skia]
G[CPU / OpenGL ES / Vulkan]
H[Surface BufferQueue]
A -->|驱动| B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> H
end
subgraph SystemServer[system_server]
I[WindowManagerService]
end
subgraph Graphics[图形与显示系统]
J[SurfaceFlinger]
K[RenderEngine / Hardware Composer]
L[显示设备]
end
H -->|提交 Buffer| J
I -->|窗口元数据| J
J --> K
K --> L
总结
Android View 渲染到屏幕,可以拆成四步。
第一步:描述页面
flowchart LR
A[View] --> B[Canvas]
View 通过 Canvas 描述文字、图片、路径和图形。
第二步:生成像素
flowchart TD
A[Canvas] --> B{绘制模式}
B -->|软件| C[Skia Raster + CPU]
B -->|硬件| D[RenderNode + HWUI + Skia]
D --> E[OpenGL ES / Vulkan + GPU]
第三步:提交画面
flowchart LR
A[渲染结果] --> B[Surface]
B --> C[BufferQueue]
Surface 将生成的图形 Buffer 提交给消费者。
第四步:合成到屏幕
flowchart LR
A[多个 Surface Buffer] --> B[SurfaceFlinger]
C[WindowManagerService] -->|窗口元数据| B
B --> D[GPU / Hardware Composer]
D --> E[屏幕]
完整过程可以浓缩成一句话:
View 通过 Canvas 描述绘制,Skia 使用 CPU 或 GPU 生成图像,Surface 通过 BufferQueue 提交图像,SurfaceFlinger 根据 WMS 提供的窗口信息完成合成并输出到屏幕。
若需要先理解 Activity、Window、DecorView 和 ViewRootImpl 的关系,请阅读:
Android 界面体系:Activity、Window、View 与 ViewRootImpl
参考资料
- Android Developers:硬件加速
- Android Developers:RenderNode API
- AOSP:Android 图形架构
- AOSP:SurfaceFlinger 与 WindowManager
- AOSP:BufferQueue 与 Gralloc
- AOSP:Surface 与 SurfaceHolder
- AOSP:SurfaceView 与 GLSurfaceView
- AOSP:TextureView
- AOSP:EGLSurface 与 OpenGL ES
- AOSP:Hardware Composer HAL
- Skia:SkCanvas Creation
- Skia API:SkCanvas
说明:Android 图形栈持续演进,不同 Android 版本和设备厂商可能采用不同的 GPU 后端、Buffer 管理与合成策略。本文重点描述稳定的架构职责和通用数据流。
Android 图形显示系统:Canvas、Skia、Surface与SurfaceFlinger
https://lautung.com/archives/3B2pIH0c
评论