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

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

相关推荐
StarShip1 小时前
第二阶段:RenderThread 渲染处理
android
通玄2 小时前
Jetpack Compose 入门系列(一):从零搭建到基础控件使用
android
StarShip2 小时前
Android 图形渲染流水线完整架构与执行流程分析
android
流年如夢2 小时前
类和对象(上)
android·java·开发语言
用户86022504674723 小时前
从入门到进阶的 React Native 实战指南
android·前端
沐言人生3 小时前
ReactNative 源码分析10——Native View创建流程createView
android·react native
问心无愧05133 小时前
ctf show web入门98
android·前端·笔记
李斯维3 小时前
Jetpack 生命周期组件 Lifecycle 的设计思想和使用
android·android studio·android jetpack
Mr YiRan3 小时前
Android构建优化:基于Git Diff+TaskGraph
android·git·elasticsearch