第二阶段:RenderThread 渲染处理

2.1 概述

RenderThread 是 Android 引入的专用渲染线程,与 UI 线程并行运行。它的核心任务是将第一阶段构建的 DisplayList 解析并执行实际的 GPU 渲染操作,最终产出 GraphicBuffer 。RenderThread 通过 RenderProxy 与 UI 线程通信,通过 CanvasContext 管理渲染上下文,通过 IRenderPipeline 的具体实现(OpenGL/Vulkan)执行底层图形 API 调用。

2.2 调用链总览

css 复制代码
UI Thread:
  ThreadedRenderer.syncAndDrawFrame()
    └── RenderProxy.syncAndDrawFrame()          // JNI 调用
         └── DrawFrameTask.drawFrame()          // 提交任务到 RenderThread
              └── postAndWait()                 // 投递并等待

RenderThread:
  DrawFrameTask.run()                           // RenderThread 执行
    ├── syncFrameState()                        // 同步 DisplayList 状态
    │    └── CanvasContext.prepareTree()        // 准备渲染树
    │         └── RenderNode.prepareTree()      // 递归准备所有节点
    └── CanvasContext.draw()                    // 执行渲染
         └── IRenderPipeline.draw()             // 多态分发
              ├── SkiaOpenGLPipeline.draw()     // OpenGL 实现
              │    ├── 绑定 FBO 0
              │    ├── renderFrame()            // 遍历 RenderNode 树
              │    ├── FlushAndSubmit()         // 提交 GPU 命令
              │    └── swapBuffers()            // eglSwapBuffers
              └── SkiaVulkanPipeline.draw()     // Vulkan 实现

2.3 关键源码分析

2.3.1 RenderProxy --- UI 线程与 RenderThread 的桥梁

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

arduino 复制代码
class RenderProxy {
public:
    RenderProxy(bool opaque, RenderNode* rootNode, IContextFactory* contextFactory,
                bool useIpcCanvas = false);

RenderProxy 是 UI 线程操作 RenderThread 的代理对象。其关键成员:

  • mRenderThread --- 全局唯一的 RenderThread 实例引用

  • mContext --- 指向 CanvasContext,管理渲染上下文

  • mDrawFrameTask --- 可复用的帧绘制任务

核心方法 syncAndDrawFrame()

csharp 复制代码
    int syncAndDrawFrame();

该方法内部调用 mDrawFrameTask.drawFrame(),将帧绘制任务投递到 RenderThread 的工作队列中。

2.3.2 DrawFrameTask --- 帧绘制任务

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

kotlin 复制代码
class DrawFrameTask {

DrawFrameTask 是一个可复用的超级任务,管理跨帧的状态。其核心方法 drawFrame()

ini 复制代码
int DrawFrameTask::drawFrame() {
    LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
    mSyncResult = SyncResult::OK;
    mSyncQueued = systemTime(SYSTEM_TIME_MONOTONIC);
    postAndWait();
    return mSyncResult;
}

postAndWait() 将任务投递到 RenderThread 的工作队列,并阻塞 UI 线程等待渲染完成:

scss 复制代码
void DrawFrameTask::postAndWait() {
    ATRACE_CALL();
    AutoMutex _lock(mLock);
    mRenderThread->queue().post([this]() { run(); });
    mSignal.wait(mLock);
}

2.3.3 DrawFrameTask.run --- 渲染主流程

源码路径 : frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp

ini 复制代码
void DrawFrameTask::run() {
    // ... preGetFrame 优化 ...
    {
        TreeInfo info(TreeInfo::MODE_FULL, *mContext);
        info.forceDrawFrame = mForceDrawFrame;
        mForceDrawFrame = false;
        canUnblockUiThread = syncFrameState(info);
        canDrawThisFrame = !info.out.skippedFrameReason.has_value();
        solelyTextureViewUpdates = info.out.solelyTextureViewUpdates;
    }
    // ...
    if (CC_LIKELY(canDrawThisFrame)) {
        dequeueBufferDuration = context->draw(solelyTextureViewUpdates);
    }
    // ...
}

该方法分为两个核心阶段:

  1. syncFrameState --- 同步 UI 线程构建的 DisplayList 到 RenderThread

  2. CanvasContext::draw --- 执行实际的 GPU 渲染

2.3.4 syncFrameState --- 同步 DisplayList 状态

scss 复制代码
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
    ATRACE_CALL();
    int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
    // ...
    mRenderThread->timeLord().vsyncReceived(vsync, intendedVsync, vsyncId, frameDeadline, frameInterval);
    bool canDraw = mContext->makeCurrent();
    mContext->unpinImages();
    // 应用 DeferredLayerUpdater
    for (size_t i = 0; i < mLayers.size(); i++) {
        if (mLayers[i]) {
            mLayers[i]->apply();
        }
    }
    mLayers.clear();
    mContext->setContentDrawBounds(mContentDrawBounds);
    mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
    // ...
}

关键操作:

  1. makeCurrent() --- 确保 EGL/Vulkan 上下文在当前线程上激活

  2. prepareTree() --- 递归遍历 RenderNode 树,同步 DisplayList 状态

2.3.5 CanvasContext.prepareTree --- 准备渲染树

源码路径 : frameworks/base/libs/hwui/renderthread/CanvasContext.cpp

scss 复制代码
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued,
                                RenderNode* target) {
    mRenderThread.removeFrameCallback(this);
    // ...
    mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
    mCurrentFrameInfo->markSyncStart();
    info.damageAccumulator = &mDamageAccumulator;
    info.layerUpdateQueue = &mLayerUpdateQueue;
    // ...
    for (const sp<RenderNode>& node : mRenderNodes) {
        info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY);
        node->prepareTree(info);
    }
    // ...
    // 预留缓冲区
    if (!info.out.skippedFrameReason) {
        int err = mNativeSurface->reserveNext();
        if (err != OK) {
            info.out.skippedFrameReason = SkippedFrameReason::NoBuffer;
        }
    }
    // 注册下一帧回调(如果有动画)
    if (info.out.hasAnimations || info.out.skippedFrameReason) {
        if (!info.out.requiresUiRedraw) {
            mRenderThread.postFrameCallback(this);
        }
    }
}

prepareTree 的核心职责:

  1. 导入 UI 线程的帧信息(VSync 时间、帧起始时间等)

  2. 递归调用 RenderNode::prepareTree() --- 同步每个节点的 DisplayList、属性更新、动画状态

  3. 预留缓冲区 --- 通过 mNativeSurface->reserveNext() 从 BufferQueue 中 dequeue 一个 GraphicBuffer

  4. 注册下一帧回调 --- 如果存在 RT 驱动的动画,注册下一帧的 FrameCallback

2.3.6 CanvasContext.draw --- 执行渲染

scss 复制代码
nsecs_t CanvasContext::draw(bool solelyTextureViewUpdates) {
    // ...
    SkRect dirty;
    mDamageAccumulator.finish(&dirty);
    // ...
    mCurrentFrameInfo->markIssueDrawCommandsStart();
    Frame frame = getFrame();
    SkRect windowDirty = computeDirtyRect(frame, &dirty);
    // ...
    {
        drawResult = mRenderPipeline->draw(
                frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
                mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
                &(profiler()), mBufferParams, profilerLock());
    }
    // ...
    bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult, windowDirty,
                                                 mCurrentFrameInfo, &requireSwap);
    // ...
}

核心步骤:

  1. 计算脏区域 --- 通过 DamageAccumulator 确定需要重绘的区域

  2. 获取 Frame --- 通过 getFrame() 获取当前帧的缓冲区信息(宽高、格式等)

  3. 调用 Pipeline.draw() --- 执行实际的渲染命令

  4. swapBuffers --- 交换前后缓冲区,将渲染结果提交给 SurfaceFlinger

2.3.7 SkiaOpenGLPipeline.draw --- OpenGL 渲染实现

源码路径 : frameworks/base/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp

rust 复制代码
IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
        const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
        const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
        const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
        const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
        const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
    // 设置脏区域
    if (!isCapturingSkp() && !mHardwareBuffer) {
        mEglManager.damageFrame(frame, dirty);
    }
    // 创建 SkSurface 包装 FBO 0
    GrGLFramebufferInfo fboInfo;
    fboInfo.fFBOID = 0;
    // ... 设置 colorType 和 format ...
    auto backendRT = GrBackendRenderTargets::MakeGL(frame.width(), frame.height(), 0,
                                                    STENCIL_BUFFER_SIZE, fboInfo);
    sk_sp<SkSurface> surface = SkSurfaces::WrapBackendRenderTarget(
            mRenderThread.getGrContext(), backendRT, getSurfaceOrigin(),
            colorType, mSurfaceColorSpace, &props);
    // 更新光照信息
    LightingInfo::updateLighting(localGeometry, lightInfo);
    // 执行渲染
    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds,
                surface, preTransform);
    // 提交 GPU 命令
    {
        ATRACE_NAME("flush commands");
        skgpu::ganesh::FlushAndSubmit(surface);
    }
    layerUpdateQueue->clear();
    return {true, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd{}};
}

关键步骤解析:

  1. damageFrame --- 通过 EGL 的 EGL_KHR_swap_buffers_with_damage 扩展告知驱动脏区域

  2. WrapBackendRenderTarget --- 将 OpenGL 的默认帧缓冲(FBO 0)包装为 Skia 的 SkSurface,使 Skia 可以直接渲染到屏幕缓冲区

  3. renderFrame --- 调用 SkiaPipeline::renderFrame(),遍历 RenderNode 树并执行绘制

  4. FlushAndSubmit --- 将 Skia 记录的 GPU 命令刷新到 OpenGL 驱动队列

2.3.8 SkiaPipeline.renderFrame --- 渲染帧

源码路径 : frameworks/base/libs/hwui/pipeline/skia/SkiaPipeline.cpp

c 复制代码
    void renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
                     const std::vector<sp<RenderNode>>& nodes, bool opaque,
                     const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
                     const SkMatrix& preTransform);

renderFrame 内部调用 renderFrameImpl,其核心逻辑:

c 复制代码
    void renderFrameImpl(const SkRect& clip,
                         const std::vector<sp<RenderNode>>& nodes, bool opaque,
                         const Rect& contentDrawBounds, SkCanvas* canvas,
                         const SkMatrix& preTransform);

renderFrameImpl 遍历 mRenderNodes 列表,为每个 RenderNode 创建 RenderNodeDrawable 并调用 forceDraw() 执行绘制。RenderNodeDrawable 会递归遍历子节点,将 DisplayList 中的绘制操作回放到 SkCanvas 上。

2.3.9 SkiaOpenGLPipeline.swapBuffers --- 提交缓冲区

rust 复制代码
bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, IRenderPipeline::DrawResult& drawResult,
                                     const SkRect& screenDirty, FrameInfo* currentFrameInfo,
                                     bool* requireSwap) {
    currentFrameInfo->markSwapBuffers();
    if (mHardwareBuffer) {
        return false;
    }
    *requireSwap = drawResult.success || mEglManager.damageRequiresSwap();
    if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) {
        return false;
    }
    return *requireSwap;
}

swapBuffers 调用 EglManager::swapBuffers(),最终调用 eglSwapBuffersWithDamageKHR(),将渲染完成的缓冲区提交给 SurfaceFlinger。

2.3.10 RenderThread 的 VSync 驱动

源码路径 : frameworks/base/libs/hwui/renderthread/RenderThread.cpp

RenderThread 通过 AChoreographer 监听 VSync 信号:

ini 复制代码
void RenderThread::extendedFrameCallback(const AChoreographerFrameCallbackData* cbData,
                                         void* data) {
    RenderThread* rt = reinterpret_cast<RenderThread*>(data);
    size_t preferredFrameTimelineIndex =
            AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(cbData);
    AVsyncId vsyncId = AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
            cbData, preferredFrameTimelineIndex);
    int64_t frameDeadline = AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
            cbData, preferredFrameTimelineIndex);
    int64_t frameTimeNanos = AChoreographerFrameCallbackData_getFrameTimeNanos(cbData);
    int64_t frameInterval = AChoreographer_getFrameInterval(rt->mChoreographer);
    rt->frameCallback(vsyncId, frameDeadline, frameTimeNanos, frameInterval);
}

frameCallback 根据 VSync 时间戳计算回调执行时间点,通过 queue().postAt() 在精确的时间点调度 dispatchFrameCallbacks(),从而驱动 RT 动画的帧更新:

scss 复制代码
void RenderThread::frameCallback(int64_t vsyncId, int64_t frameDeadline, int64_t frameTimeNanos,
                                 int64_t frameInterval) {
    mVsyncRequested = false;
    if (timeLord().vsyncReceived(frameTimeNanos, frameTimeNanos, vsyncId, frameDeadline,
                                 frameInterval) &&
        !mFrameCallbackTaskPending) {
        mFrameCallbackTaskPending = true;
        // 计算调度时间点
        const auto runAt = (frameTimeTimePoint + (timeUntilDeadline / 4));
        queue().postAt(toNsecs_t(runAt.time_since_epoch()).count(),
                       [this]() { dispatchFrameCallbacks(); });
    }
}

2.4 缓冲区流转机制

2.4.1 GraphicBuffer 的 dequeue/queue 流程

css 复制代码
RenderThread 渲染流程中的缓冲区操作:

1. prepareTree() 阶段:
   mNativeSurface->reserveNext()
     └── ANativeWindow_dequeueBuffer()
          └── BufferQueueProducer::dequeueBuffer()
               └── 从 BufferQueue 中获取一个空闲的 GraphicBuffer

2. draw() 阶段:
   Skia 渲染到 SkSurface (包装了 GraphicBuffer)
     └── FlushAndSubmit() → GPU 渲染完成

3. swapBuffers() 阶段:
   eglSwapBuffersWithDamageKHR()
     └── ANativeWindow_queueBuffer()
          └── BufferQueueProducer::queueBuffer()
               └── 将填充好数据的 GraphicBuffer 入队
               └── 通知 Consumer (SurfaceFlinger) 有新帧可用

2.4.2 BLASTBufferQueue 机制

现代 Android 使用 BLASTBufferQueue(Buffer-Layer-Application-Surface-Transaction)来管理缓冲区:

arduino 复制代码
    virtual void setBLASTBufferQueue(const sp<BLASTBufferQueue>& bbq) { mBLASTBufferQueue = bbq; }
    bool syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
                             bool acquireSingleBuffer = true) override {
        if (mBLASTBufferQueue != nullptr) {
            return mBLASTBufferQueue->syncNextTransaction(callback, acquireSingleBuffer);
        }
    }

BLASTBufferQueue 允许将缓冲区提交与 SurfaceTransaction 同步,实现缓冲区更新和窗口属性更新的原子性。

2.5 渲染管线选择

CanvasContext 根据系统配置选择具体的渲染管线实现:

arduino 复制代码
#include "pipeline/skia/SkiaCpuPipeline.h"
#include "pipeline/skia/SkiaGpuPipeline.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
#include "pipeline/skia/SkiaVulkanPipeline.h"
  • SkiaOpenGLPipeline --- 基于 OpenGL ES 3.x 的 GPU 渲染管线

  • SkiaVulkanPipeline --- 基于 Vulkan 的 GPU 渲染管线

  • SkiaCpuPipeline --- 基于 CPU 的软件渲染管线(用于无 GPU 环境)

2.6 关键类关系图

scss 复制代码
RenderProxy
  └── mDrawFrameTask (DrawFrameTask)
       └── run()
            ├── syncFrameState()
            │    └── CanvasContext.prepareTree()
            │         └── RenderNode.prepareTree()  [递归所有节点]
            └── CanvasContext.draw()
                 └── mRenderPipeline->draw()
                      ├── SkiaOpenGLPipeline::draw()
                      │    ├── WrapBackendRenderTarget(FBO 0)
                      │    ├── renderFrame() → RenderNodeDrawable::forceDraw()
                      │    ├── FlushAndSubmit()
                      │    └── swapBuffers() → eglSwapBuffersWithDamageKHR()
                      └── SkiaVulkanPipeline::draw()

2.7 总结

RenderThread 渲染处理阶段的核心产出是 GraphicBuffer(填充了像素数据的图形缓冲区)。该阶段的关键设计:

  1. 异步渲染 --- RenderThread 与 UI 线程并行执行,UI 线程提交 DisplayList 后即可继续处理下一帧

  2. GPU 渲染 --- 通过 Skia 将 DisplayList 转换为 OpenGL/Vulkan 命令,利用 GPU 硬件加速

  3. 缓冲区管理 --- 通过 BufferQueue 的 dequeue/queue 机制管理 GraphicBuffer 的生命周期

  4. 脏区域优化 --- 仅重绘脏区域,减少 GPU 负载

  5. Fence 同步 --- 通过 EGL/ Vulkan Fence 机制确保 GPU 渲染完成后再提交缓冲区

相关推荐
plainGeekDev15 小时前
文件读写(Java IO)→ Kotlin 扩展函数
android·java·kotlin
s_nshine16 小时前
释放C盘,迁移studio相关数据到其他盘
android·windows·android studio·内存·c盘
韩曙亮16 小时前
【Flutter】Flutter 中的 Android / iOS 特殊配置 ① ( 网络权限配置 | HTTP 明文传输配置 | 应用名称配置 )
android·网络·flutter·http·ios·网络权限
_李小白16 小时前
【android opencv学习笔记】Day 31:提取轮廓之Canny算法
android·opencv·学习
hashiqimiya17 小时前
每日android布局xml文件
android·xml·gitee
m0_7381207218 小时前
渗透测试基础——PHP 序列化数据结构与反序列化机制详解
android·服务器·网络·数据结构·安全·php
故渊at18 小时前
第二板块:Android 四大组件标准化学理 | 第十一篇:组件间通信(IPC)与 Binder 深度解析
android·binder·组件化·组件间通信
ZC跨境爬虫18 小时前
跟着 MDN 学JavaScript day_10:数组——数据的有序集合
android·java·开发语言·前端·javascript
消失的旧时光-194319 小时前
Kotlin 协程设计思想(九):Flow 到底是什么?为什么 suspend 函数还需要 Flow?
android·kotlin·协程·协程异常