第一阶段:应用层视图绘制

1.1 概述

应用层视图绘制阶段运行在 UI Thread(主线程) 上,是整个图形渲染流水线的起点。该阶段的核心任务是将应用开发者编写的 View 树遍历并转换为 GPU 可理解的 DisplayList (显示列表),然后通过 RenderProxy 将 DisplayList 提交给 RenderThread 进行异步渲染。

1.2 调用链总览

scss 复制代码
Choreographer.doFrame()
  └── ViewRootImpl.doTraversal()
       └── ViewRootImpl.performTraversals()
            ├── performMeasure()       // 测量阶段
            ├── performLayout()        // 布局阶段
            └── performDraw()          // 绘制阶段
                 └── ThreadedRenderer.draw()
                      ├── updateRootDisplayList()  // 构建 DisplayList
                      └── syncAndDrawFrame()       // 提交到 RenderThread

1.3 关键源码分析

1.3.1 Choreographer 驱动帧循环

Choreographer 是 Android 帧循环的调度中心,它监听 VSync 信号,在每次垂直同步到来时回调 doFrame() 方法。

源码路径 : frameworks/base/core/java/android/view/Choreographer.java

ViewRootImpl 通过 scheduleTraversals() 向 Choreographer 注册一个 TraversalRunnable,该 Runnable 在下一个 VSync 到来时被调用:

csharp 复制代码
    private void performTraversals(long frameTimeNanos) {
        mLastPerformTraversalsSkipDrawReason = null;
        // cache mView since it is used so much below below...
        final View host = mView;
        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals");
            host.debug();
        }
        if (host == null || !mAdded) {
            mLastPerformTraversalsSkipDrawReason = host == null ? "no_host" : "not_added";
            return;
        }

1.3.2 performTraversals --- 三阶段调度

performTraversals() 是 View 树遍历的核心调度方法,依次执行三个子阶段:

  1. performMeasure --- 自顶向下遍历 View 树,计算每个 View 的期望尺寸

  2. performLayout --- 自顶向下遍历 View 树,确定每个 View 的位置和大小

  3. performDraw --- 触发实际的绘制操作

performDraw 阶段,关键逻辑如下(位于 ViewRootImpl.java 约第 8456 行):

kotlin 复制代码
                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);

这里调用了 ThreadedRenderer.draw(),标志着从 View 系统进入 HWUI 渲染引擎。

1.3.3 ThreadedRenderer.draw --- 构建 DisplayList 并提交

源码路径 : frameworks/base/core/java/android/view/ThreadedRenderer.java

ini 复制代码
    void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
        final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
        attachInfo.mViewRootImpl.mViewFrameInfo.markDrawStart();
        updateRootDisplayList(view, callbacks);

该方法包含两个核心步骤:

步骤 1: updateRootDisplayList --- 构建 DisplayList

updateRootDisplayList() 负责遍历 View 树,将每个 View 的绘制操作记录到 RenderNode 的 DisplayList 中。其核心流程如下:

  1. 获取或创建根 RenderNodeSkiaRecordingCanvas

  2. 调用 View.draw(canvas) 触发 View 树的递归绘制

  3. 每个 View 的 onDraw() 方法将绘制指令(drawRect、drawBitmap、drawText 等)记录到 Canvas 中

  4. SkiaRecordingCanvas 将这些指令编码为 Skia 的绘制操作,存储在 SkiaDisplayList

关键类:

  • RenderNode --- 对应 View 树中的一个节点,持有 DisplayList 和渲染属性

  • SkiaRecordingCanvas --- 继承自 SkiaCanvas,负责将绘制 API 调用记录为 DisplayList

步骤 2: syncAndDrawFrame --- 提交到 RenderThread

ini 复制代码
            syncResult = syncAndDrawFrame(frameInfo);

syncAndDrawFrame() 是 JNI 调用,进入 Native 层的 RenderProxy::syncAndDrawFrame()。该方法将构建好的 DisplayList 同步到 RenderThread,并触发异步渲染。

1.3.4 SkiaRecordingCanvas --- DisplayList 的录制器

源码路径 : frameworks/base/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h

arduino 复制代码
class SkiaRecordingCanvas : public SkiaCanvas {
public:
    explicit SkiaRecordingCanvas(uirenderer::RenderNode* renderNode, int width, int height) {
        initDisplayList(renderNode, width, height);
    }

SkiaRecordingCanvas 的核心机制:

  1. initDisplayList --- 初始化或复用 SkiaDisplayList,并绑定 RecordingCanvas(Skia 的 SkLiteRecorder

  2. 录制过程 --- 所有 drawBitmap()drawRect()drawRenderNode() 等调用被记录为 Skia 的绘制操作(SkDrawable 子类)

  3. finishRecording --- 完成录制,将 DisplayList 关联到 RenderNode

arduino 复制代码
void SkiaRecordingCanvas::initDisplayList(uirenderer::RenderNode* renderNode, int width,
                                          int height) {
    mCurrentBarrier = nullptr;
    LOG_FATAL_IF(mDisplayList.get() != nullptr);
    if (renderNode) {
        mDisplayList = renderNode->detachAvailableList();
    }
    if (!mDisplayList) {
        mDisplayList.reset(new SkiaDisplayList());
    }
    mDisplayList->attachRecorder(&mRecorder, SkIRect::MakeWH(width, height));
    SkiaCanvas::reset(&mRecorder);

关键点:

  • renderNode->detachAvailableList() --- 尝试复用上一帧的 DisplayList,避免重复分配

  • mDisplayList->attachRecorder() --- 将 Skia 的 RecordingCanvas 绑定到 DisplayList

  • SkiaCanvas::reset(&mRecorder) --- 将 Canvas 的绘制目标设置为 recorder

1.3.5 RenderNode --- 视图节点的渲染表示

源码路径 : frameworks/base/libs/hwui/RenderNode.h

RenderNode 是 View 在渲染层的对应表示,每个 View 对应一个 RenderNode。其关键职责:

  • 持有 SkiaDisplayList(录制好的绘制指令集合)

  • 管理渲染属性(位置、变换、透明度、裁剪区域等)

  • 维护子 RenderNode 列表(对应 View 树的层级结构)

  • 支持 DisplayList 的缓存与复用

当 View 树中的某个 View 发生局部更新时,只有对应的 RenderNode 需要重新录制 DisplayList,其他节点可以复用上一帧的 DisplayList,这是 Android 渲染性能优化的核心机制之一。

1.4 DisplayList 的构建与缓存机制

1.4.1 构建流程

scss 复制代码
View.draw(canvas)
  └── View.onDraw(canvas)                    // 应用自定义绘制
  └── View.dispatchDraw(canvas)              // 绘制子 View
       └── drawChild(canvas, child, ...)
            └── child.draw(canvas)           // 递归子 View
                 └── RenderNode.record(...)  // 记录到 DisplayList

1.4.2 缓存复用

当 View 树没有发生变化时(通过 View.mPrivateFlags 中的 PFLAG_INVALIDATED 标志判断),RenderNode 可以直接复用上一帧的 DisplayList,跳过录制过程。这通过 renderNode->detachAvailableList() 实现:

  • 如果 RenderNode 持有上一帧的 DisplayList,则 detach 并复用

  • 否则创建新的 SkiaDisplayList

1.4.3 脏区域追踪

DamageAccumulator 负责追踪需要重绘的脏区域。在 CanvasContext::prepareTree() 中,脏区域信息被累积并传递给渲染管线,用于优化后续的渲染范围:

ini 复制代码
    info.damageAccumulator = &mDamageAccumulator;
    info.colorArea = &mColorArea;
    info.layerUpdateQueue = &mLayerUpdateQueue;
    info.damageGenerationId = mDamageId++;

1.5 缓冲区流转起点

在应用层绘制阶段结束时,DisplayList 已经构建完成,但尚未产生任何 GraphicBuffer。真正的缓冲区操作发生在 RenderThread 阶段。应用层通过 syncAndDrawFrame() 将 DisplayList 的引用传递给 RenderThread,UI 线程随即可以继续处理下一帧的输入事件,实现 UI 线程与渲染线程的并行执行。

1.6 关键类关系图

scss 复制代码
ViewRootImpl
  └── mAttachInfo.mThreadedRenderer (ThreadedRenderer)
       └── mRootNode (RenderNode)              // 根节点
            └── mDisplayList (SkiaDisplayList) // 录制的绘制指令
                 └── mRecorder (RecordingCanvas) // Skia 录制器
                      └── drawOps...           // 具体的绘制操作

1.7 总结

应用层视图绘制阶段的核心产出是 DisplayList,它是对 View 树绘制操作的延迟记录。通过将绘制指令录制为 DisplayList,Android 实现了:

  1. UI 线程与渲染线程的解耦 --- UI 线程只需构建 DisplayList,无需等待 GPU 完成

  2. 增量更新 --- 只有脏区域对应的 RenderNode 需要重新录制

  3. DisplayList 复用 --- 未发生变化的节点可以直接复用上一帧的 DisplayList

  4. 跨进程传递 --- DisplayList 最终通过 RenderProxy 跨线程传递给 RenderThread

相关推荐
故渊at2 小时前
第二板块:Android 四大组件标准化学理 | 第六篇:四大组件架构总论与 Manifest 规范
android·架构·zygote·manifest·四大组件
Jinkxs3 小时前
Python基础 - 文件的写入操作 write与writelines方法
android·服务器·python
jason.zeng@15022073 小时前
(第二讲)Android开发取摄像头流的基础(ImageAnalysis)
android
敲代码的瓦龙4 小时前
操作系统?Android与Linux!!!
android·linux·运维
愚公搬代码4 小时前
【愚公系列】《移动端AI应用开发》017-Android端应用开发(网络通信与API集成)
android·人工智能
say_fall4 小时前
可编程中断控制器8259A工作方式超详细解析
android·开发语言·学习·硬件架构·硬件工程
甜瓜看代码6 小时前
SystemUI 启动与组成机制
android·源码·源码阅读
黄林晴7 小时前
Kotlin 2.4.0 正式稳定!Android 升级、Compose、KMP 全变化详解
android·kotlin
恋猫de小郭8 小时前
Android 官方给 Compose 搞了个不需要 UI 环境的 Composable
android·前端·flutter
珊瑚里的鱼9 小时前
C++的强制类型转换
android·开发语言·c++