Android 图形渲染框架

Android 的图形渲染框架负责将应用程序的 UI 元素(View、Canvas、OpenGL 等)渲染到屏幕上。它的核心目标是高效、流畅地将图形内容从应用程序传递到硬件显示设备。整个框架涉及多个层次,包括应用层、系统层、图形 API 以及硬件层。

用户交互、动画、布局变化等会调用 View.invalidate() 或 requestLayout()。Choreographer 会在下一帧(VSync 信号到来时)调用 ViewRootImpl.doFrame()。这个绘制必须在VSync 屏幕刷新信号发送时间间隔完成,否则会导致掉帧。

绘制包括以下三个流程:

  1. measure:测量每个 View 的大小(View.measure() → onMeasure())。
  2. layout:确定 View 在父布局中的位置(View.layout() → onLayout())。
  3. 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 个关键线程:

  1. UI Thread:执行 View 的 measure/layout/draw,生成 DisplayList。
  2. RenderThread:处理 GPU 绘制命令,渲染到 Surface。
  3. SurfaceFlinger Thread:合成多个 Surface,提交到屏幕。
  4. HWC Thread:硬件合成,驱动输出到屏幕。

根据这个过程,可以得出一些性能优化的方向。由于绘制内容在 UI 主线程生成,开发者应避免阻塞 UI 线程的操作,如复杂计算或 I/O。View 的绘制指令被缓存为 DisplayList 以避免重复计算,开发者可以通过仅重绘发生变化的 View(通过 invalidate() 或 requestLayout() )来优化重绘,同时尽量减少 View 树的变化,优化布局复杂度。同一像素被多次绘制会导致性能浪费,可以使用 clipRect 或 clipPath 限制绘制区域,避免不必要的背景重叠,同时使用 Android Studio 的 GPU 渲染分析工具来检测 Overdraw 。

一些常见的渲染问题及解决方法:

  1. 掉帧(Jank)。原因:UI 线程或 RenderThread 过载。解决:优化布局、减少复杂动画、使用异步任务。
  2. 画面撕裂。原因:渲染与屏幕刷新不同步。解决:确保 VSync 正常工作,避免禁用硬件加速。
  3. 高内存占用。原因:过多的 GraphicBuffer 或复杂的 View 树。解决:优化 View 层级,使用轻量级组件(如 ConstraintLayout)。
  4. 兼容性问题。原因:不同设备对 OpenGL ES 或 Vulkan 的支持不同。解决:提供软件渲染作为回退方案,测试多款设备。