根据博客 Android-View 绘制原理(01)-JAVA层分析_android view draw原理分析-CSDN博客
提出了我的疑问 Canvas RenderNode updateDisplayListDirty 这些东西的关系
您的理解在基本方向上是对的,但让我详细解释一下 Android 中 updateDisplayListDirty
、指令集合、Canvas 和 RenderNode 之间的关系:
RenderNode 与渲染过程
在 Android 的渲染系统中:
- RenderNode:
- 是一个存储渲染操作的容器
- 包含了视图的绘制命令和属性信息
- 可以被缓存和重用,提高渲染效率
- DisplayList:
- 实际上是 RenderNode 内部的绘制命令列表
- 存储了一系列需要执行的渲染指令
- 通过
updateDisplayListDirty
方法更新这些指令
- Canvas:
- 是应用层与渲染系统交互的接口
- 提供了各种绘制方法(drawRect、drawText 等)
- 当我们调用 Canvas 的绘制方法时,它会转换为渲染指令
updateDisplayListDirty 与 Canvas 的关系
updateDisplayListDirty
方法在 View 类中实现,主要作用是:
- 创建或获取一个 RenderNode
- 使用 DisplayListCanvas(特殊的 Canvas 实现)录制绘制指令
- 当视图调用 onDraw() 方法时,它使用提供的 Canvas 执行绘制
- 这些绘制命令被记录到 DisplayList 中
- 最终生成或更新 RenderNode
Canvas 和 DisplayList 不是可替代关系,而是协作关系:
- Canvas 是绘制接口,应用通过它表达"画什么"
- DisplayList 是指令集合,记录"如何画"
- RenderNode 是整个结构的容器,包含了 DisplayList 和其他渲染属性
所以不是"有一个就可以了",而是它们在渲染管线中扮演不同角色:Canvas 是应用用来绘制的工具,而 DisplayList 是记录这些绘制指令的容器,两者共同构成了 Android 的高效渲染系统。
看完后我的疑问变成了,既然 updateDisplayListDirty
是一个指令集合 在硬件加速的情况下,一个渲染当VSYNC信号来了,进行三大流程进行获取surfaceControl然后进行 绘制,绘制时候先新建RenderNode 然后获取 RenderNode 的
RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
RecordingCanvas 然后把它变成后续的 Canvas
最后
public void endRecording() {if (mCurrentRecordingCanvas == null) {throw new IllegalStateException("No recording in progress, forgot to call #beginRecording()?");}RecordingCanvas canvas = mCurrentRecordingCanvas;mCurrentRecordingCanvas = null;canvas.finishRecording(this);canvas.recycle();}
finishRecording 把 当前的 RenderNode 传过去 然后进行绘制
那么我的问题是 之后surfaceFlinger是否绘制的是指令集合?是否是当RecordingCanvas 执行一次生成一个指令就绘制一部分还是 finishRecording 后一起进行绘制?
您的问题涉及到 Android 渲染流水线的关键细节,我来详细解释:
finishRecording 与渲染流程
在 Android 的硬件加速渲染流程中,当 finishRecording()
被调用时:
- 指令集完成阶段:
finishRecording()
标志着 RecordingCanvas 已经完成所有绘制命令的记录- 此时,所有来自
onDraw()
的绘制操作已经被转换为渲染指令并存储在 RenderNode 中 - 这些指令并不会立即触发绘制
- 批处理渲染模式:
- SurfaceFlinger 不会 在每条指令生成后立即渲染
- 系统采用批处理模式,收集完所有指令后一次性处理
- 这种方式大大提高了渲染效率,避免了频繁的状态切换和上下文切换
- 渲染时机:
- 在
ViewRootImpl.performTraversals()
完成后,会调用ThreadedRenderer.draw()
- 此时,所有视图的 RenderNode 都已准备就绪
- 系统会将这些渲染命令通过 HWUI 传递给 GPU
- SurfaceFlinger 会在下一个 VSYNC 信号到来时合成并显示结果
- 在
- SurfaceFlinger 处理:
- SurfaceFlinger 接收的是已经处理过的图层数据
- 它主要负责合成多个应用的图层,而不是执行具体的绘制指令
- 实际的指令执行发生在 GPU 上,在应用进程的渲染线程中
所以,回答您的核心问题:系统是在 finishRecording()
后,将完整的指令集合一次性提交给渲染管线进行处理,而不是每生成一条指令就执行一次渲染。这种批处理模式是现代图形渲染系统的普遍做法,可以最大限度地减少 GPU 状态切换,提高渲染效率。
需要注意的是,RecordingCanvas 记录的指令集和 SurfaceFlinger 处理的内容在概念上有区别——前者是应用内的绘制指令,后者处理的是已经渲染好的图层。
那这些指令在哪被处理?
这些渲染指令的处理发生在多个层次的渲染管线中,让我详细解释这个流程:
渲染指令的处理路径
- 应用进程中的处理:
- 当
finishRecording()
完成后,渲染指令首先在应用自己的渲染线程中处理 ThreadedRenderer
类负责协调这个过程- 具体处理由 HWUI (Hardware User Interface) 库在原生层完成
- 在这一阶段,渲染指令被转换为 GPU 可以理解的命令(如 OpenGL ES 或 Vulkan 调用)
- 当
- RenderThread 中的执行:
- Android 使用专门的 RenderThread(渲染线程)处理这些指令
- 这个线程与主线程分离,可以并行处理渲染工作
- 渲染指令在这里被组织成命令缓冲区(Command Buffer)
- 这些命令最终会通过图形驱动提交给 GPU
- GPU 执行阶段:
- GPU 接收这些渲染命令并执行实际的像素处理
- 渲染结果被写入到应用的 Surface 对应的图形缓冲区中
- 这个过程完全由 GPU 硬件加速完成
- SurfaceFlinger 的角色:
- SurfaceFlinger 不处理原始渲染指令,而是处理已渲染好的图层
- 它从各个应用获取已经渲染好的图形缓冲区
- 然后进行合成(Composition)操作,决定不同应用的图层如何叠加显示
- 最后将合成结果提交给显示硬件
具体源码路径
如果您想在源码中追踪这个处理过程:
ThreadedRenderer.nSyncAndDrawFrame()
是关键的入口点,它通过 JNI 调用原生层代码- 在原生层,
android_view_ThreadedRenderer.cpp
包含了相关实现 - 进一步深入到
libs/hwui/
目录,特别是RenderProxy
和RenderThread
相关类 - 最终的 GPU 命令提交通过图形驱动 API(如 OpenGL ES 或 Vulkan)完成
所以,渲染指令主要在应用自己的渲染线程中被处理和执行,而不是在 SurfaceFlinger 中。SurfaceFlinger 的工作是在所有应用都完成各自的渲染后,组合这些结果并显示到屏幕上。