DisplayList 与 RenderThread:异步绘制的秘密
2026/8/9大约 3 分钟渲染管线Android 14DisplayListRenderThreadHWUI
DisplayList 与 RenderThread:异步绘制的秘密
上一章看了三阶段。这一章回答:onDraw 记录的指令去了哪,为什么 RenderThread 能在 后台绘制,DisplayList 的作用是什么?
主线程记录命令,RenderThread 执行渲染
| 常见误解 | 正确理解 |
|---|---|
| 每帧都要重新执行 onDraw | 显示列表会缓存绘制指令,只有失效部分才重新记录 |
| RenderThread 是“另一个主线程” | 它专注渲染任务,与主线程并行且按帧同步 |
| DisplayList 是位图 | 它是指令列表(渲染节点树),不是像素 |
核心结论
- onDraw 把绘制指令记录到 DisplayList,不是直接画像素。
- RenderThread 执行 DisplayList,与主线程并行,减少主线程负担。
- 指令缓存复用:未失效的部分不重新记录与绘制。
- 主线程与 RenderThread 通过帧同步协调(等上一帧完成再继续)。
- 复杂视图的 DisplayList 越大,记录与失效成本越高。
记录与执行主链
逐步解释:
- 记录:主线程把绘制命令写入 RenderNode。
- 同步:RenderThread 拿到更新的指令。
- 执行:RenderThread 在 GPU 上绘制。
- 帧同步:等上一帧完成后提交新帧。
为什么能省时间
传统“每帧全部重画”在主线程串行执行,动辄几十毫秒。DisplayList 把“记录”和“执行” 分开:主线程只更新变化的指令,RenderThread 并行执行,配合缓存避免重复工作。 这也是硬件加速前后渲染性能差异的核心。
源码证据
阅读目标:确认 RenderNode 与 RenderThread 的入口。
正文指针:libs/hwui/ 的 RenderNode 与 RenderThread; View.java draw() 中记录指令的调用。
// libs/hwui/RenderThread.cpp(伪代码,行号以 r75 为准)
void RenderThread::threadLoop() {
// 处理渲染任务,等待下一帧
}这段代码证明: RenderThread 是独立线程的渲染任务循环;DisplayList 是它消费的 “任务清单”。主线程只负责更新清单。
验证与排障
环境:开发设备或模拟器;以下命令不需要 root。
adb shell dumpsys gfxinfo <包名>
adb shell setprop debug.hwui.show_dirty_regions true
adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto-trace \
-t 10s sched freq idle atrace gfx viewgfxinfo 的绘制耗时能反映 DisplayList 记录与执行;show_dirty_regions 可视化失效区域; Perfetto 的 gfx 轨道区分主线程与 RenderThread。绘制慢先看是记录慢还是执行慢。
常见误区
- “onDraw 每帧全部重画”:指令缓存会复用。
- “DisplayList 是位图”:是指令树。
- “RenderThread 在主线程里跑”:独立线程并行执行。
- “记录指令没成本”:复杂视图记录与失效都贵。
延伸问题
- RenderNode 缓存失效的粒度是什么?
- RenderThread 与主线程如何等待彼此?
- 动画为什么会让 DisplayList 反复失效?
- 如何用 trace 区分记录与执行耗时?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
libs/hwui/RenderNode.cpp | 指令树 | DisplayList |
libs/hwui/RenderThread.cpp | 线程循环 | 执行绘制 |
View.java | draw() | 指令记录 |
Choreographer.java | 帧同步 | 时序 |
公共路径:frameworks/base/libs/hwui/ 与 frameworks/base/core/java/android/view/。 行号以 r75 检索为准。
HWUI 如何调用 Skia 与 GPU,见 硬件加速。