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

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

相关推荐
Coffeeee4 小时前
闲聊几句,Android老哥们,你们多久没做技改需求了
android·程序员·代码规范
萝卜er5 小时前
Fragment 生命周期与状态恢复-《Android深水区(四)》
android
萝卜er5 小时前
Intent 显式、隐式与 PendingIntent-《Android深水区(五)》
android
Kapaseker7 小时前
一文吃透 Kotlin 集合操作符
android·kotlin
三少爷的鞋8 小时前
Main-safe:现代Android 架构真正的分水岭
android
沐怡旸17 小时前
深入解析 Android Performance Analyzer (APA) 底层架构与技术原理
android
李斯维1 天前
从历史的角度看 Android 软件架构
android·架构·android jetpack
plainGeekDev1 天前
Activity 间传值 → Navigation 参数
android·java·kotlin
用户41659673693551 天前
Android WebView 加载 file:// 离线页面调试教程
android·前端
plainGeekDev1 天前
onActivityResult → ActivityResult API
android·java·kotlin