Android 图形渲染框架
Android 的图形渲染框架负责将应用程序的 UI 元素(View、Canvas、OpenGL 等)渲染到屏幕上。它的核心目标是高效、流畅地将图形内容从应用程序传递到硬件显示设备。整个框架涉及多个层次,包括应用层、系统层、图形 API 以及硬件层。
用户交互、动画、布局变化等会调用 View.invalidate()
或
requestLayout()
。Choreographer
会在下一帧(VSync 信号到来时)调用
ViewRootImpl.doFrame()
。这个绘制必须在VSync
屏幕刷新信号发送时间间隔完成,否则会导致掉帧。
绘制包括以下三个流程:
- measure:测量每个 View
的大小(
View.measure()
→onMeasure()
)。 - layout:确定 View
在父布局中的位置(
View.layout()
→onLayout()
)。 - draw:绘制内容(
View.draw()
→dispatchDraw()
)。
绘制到 Canvas 有两种模式,一种是通过 CPU 绘制 Bitmap
位图,称为软绘制,另一种是通过 GPU
进行绘制,即硬件加速,Canvas
是
HardwareCanvas
,绘制操作会记录为渲染命令列表
DisplayList
,不直接画到屏幕。
从 Android 3.0+ 开始默认开启硬件加速模式,在这个模式下,在
View.draw()
中,绘制命令不是立即执行,而是调用
RecordingCanvas
把
drawRect
、drawBitmap
这些指令录制成 GPU
可以理解的命令列表。这样可以避免每次绘制都经过
Java → Native
的高频调用。从 Android 5.0 开始引入了
RenderThread
,通过独立线程处理 GPU 绘制,减少 UI
线程卡顿。UI 线程只生成 DisplayList,提交给 RenderThread。RenderThread
用 OpenGL ES(或 Vulkan)把 DisplayList 转换成 GPU 绘制指令,并绘制到
Surface 的缓冲区。Android 默认使用 Skia 图形库调用
OpenGL ES。从 Android 10 开始支持 Vulkan
Renderer(更高性能)。
绘制结果需要存放到一个 Surface,Surface 可以理解成显存中的画布,由
SurfaceView
、TextureView
或 Window
产生,内部持有一个 BufferQueue。BufferQueue
是生产者-消费者模型,其中的生产者是应用进程的 RenderThread,消费者是
SurfaceFlinger。BufferQueue
使用双缓冲和三缓冲工作模式,双缓冲和三缓冲的概念源自更底层的图形缓冲机制,BufferQueue
只是 Android 中承载这种机制的实现载体。在 Android 图形渲染中,CPU(UI
Thread/RenderThread)和 GPU(绘制)是异步工作的。如果只有一个缓冲区,CPU
正在写数据时,GPU 就不能读,会造成撕裂或花屏,必须等 CPU 写完,GPU
才能渲染,这样就容易卡顿,无法充分利用硬件并行性,因此需要
多缓冲 来解耦 生产者(绘制端) 和
消费者(显示端)
的速度。三缓冲比双缓冲多一个备用缓冲,防止生产者被阻塞,提升流畅度。
接下来 Android 的系统服务 SurfaceFlinger 负责把多个应用窗口的 Surface 合成到最终屏幕图像中。SurfaceFlinger 从每个应用的 BufferQueue 中取出最新的图像缓冲,这些 Surface 可能来自主界面、弹窗、系统栏等不同应用,如果硬件支持 HWC,由 Hardware Composer 合成,性能更好,否则由 GPU 合成。SurfaceFlinger 等待硬件发来的 VSync 屏幕刷新信号来提交下一帧。这就是为什么 Android 的绘制频率通常是 60Hz / 90Hz / 120Hz。
如果硬件支持,Hardware Composer HAL 提供接口让 SurfaceFlinger 把图层交给硬件直接合成,避免 GPU 额外消耗。HWC 会直接把合成结果交给显示驱动 Framebuffer。显示驱动负责把像素信号传给屏幕的 TCON(Timing Controller),最终刷新到 LCD/OLED 面板。
这其中,Skia 是 Android 的底层 2D 图形库(Chrome 也用它),它支持 CPU 渲染(软件)和GPU 渲染(OpenGL ES / Vulkan),Canvas 的所有 API(如 drawLine、drawPath)最终都会调用 Skia。
总结一下,在这个图形绘制过程中存在 4 个关键线程:
- UI Thread:执行 View 的 measure/layout/draw,生成 DisplayList。
- RenderThread:处理 GPU 绘制命令,渲染到 Surface。
- SurfaceFlinger Thread:合成多个 Surface,提交到屏幕。
- HWC Thread:硬件合成,驱动输出到屏幕。
根据这个过程,可以得出一些性能优化的方向。由于绘制内容在 UI
主线程生成,开发者应避免阻塞 UI 线程的操作,如复杂计算或 I/O。View
的绘制指令被缓存为 DisplayList
以避免重复计算,开发者可以通过仅重绘发生变化的 View(通过
invalidate()
或 requestLayout()
)来优化重绘,同时尽量减少 View
树的变化,优化布局复杂度。同一像素被多次绘制会导致性能浪费,可以使用
clipRect
或 clipPath
限制绘制区域,避免不必要的背景重叠,同时使用 Android Studio 的 GPU
渲染分析工具来检测 Overdraw 。
一些常见的渲染问题及解决方法:
- 掉帧(Jank)。原因:UI 线程或 RenderThread 过载。解决:优化布局、减少复杂动画、使用异步任务。
- 画面撕裂。原因:渲染与屏幕刷新不同步。解决:确保 VSync 正常工作,避免禁用硬件加速。
- 高内存占用。原因:过多的 GraphicBuffer 或复杂的 View 树。解决:优化 View 层级,使用轻量级组件(如 ConstraintLayout)。
- 兼容性问题。原因:不同设备对 OpenGL ES 或 Vulkan 的支持不同。解决:提供软件渲染作为回退方案,测试多款设备。