Android View绘制原理 - DrawFrameTask

前面介绍了关于绘制相关的一些组件,如RecordingCanvas,RenderThread,RenderProxy,CanvasContext,DisplayList,HardwareRender等等,这些准备工作搭建了好之后,才可以开启真正的绘制。因此我们来继续分析一帧的绘制的流程,它是由DrawFrameTask来完成的,我们还是从Java层的ViewRootImpl讲起

java 复制代码
private void performDraw() {
           ...
            boolean canUseAsync = draw(fullRedrawNeeded);
            ...
}
java 复制代码
private boolean draw(boolean fullRedrawNeeded) {
       ...
       mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
       ...
 }

从而进入到ThreadedRenderer.draw方法
frameworks/base/core/java/android/view/ThreadedRenderer.java

java 复制代码
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
       ...
       updateRootDisplayList(view, callbacks);
       ...
       int syncResult = syncAndDrawFrame(frameInfo);
       ...
 }

在draw方法里主要作了两件事件

  • updateRootDisplayList 更新UI树的DisplayList,记录Canvas的绘制操作Op,这里指的是记录到stagingDisplayList的DisplauListData
  • syncAndDrawFrame 同步UI更新到displayList,然后绘制帧,将Op绘制到SkCanvas

updateRootDisplayList这个方法之前已经详细介绍过,这就不作过多的介绍,这里需要注意是,它绘制的是RootRenderNode,它对应的View也是ViewRootImpl所持有的根 View,因此是整个树绘画的起点。这个根View的updateDisplayListIfDirty())方法将返回包含整个树更新后的RenderNode,然后直接使用drawRenderNode方法将其记录到RootNode的stagingDisplayList中去. 这个过程将在C层构建出一颗RenderNode树。

java 复制代码
 private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
        
            RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
             ...
             canvas.drawRenderNode(view.updateDisplayListIfDirty());
              ...
            } finally {
                mRootNode.endRecording();
            }
        }
    }

1 syncAndDrawFrame

我们这篇文章主要分析一下syncAndDrawFrame这个方法,这里的同步指的是stagingDisplayList数据到displayList的同步,同步完之后执行一帧的绘制。

frameworks/base/graphics/java/android/graphics/HardwareRenderer.java

java 复制代码
 public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) {
       return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length);
 }

frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp

java 复制代码
{"nSyncAndDrawFrame", "(J[JI)I", (void*)android_view_ThreadedRenderer_syncAndDrawFrame}

static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz, jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
   ...
    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
    env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
    return proxy->syncAndDrawFrame();
}

Java层会传入已经构建好的RenderProxy指针,从转换成C层的RenderProxy对象,然后调用syncAndDrawFrame方法
frameworks/base/libs/hwui/renderthread/RenderProxy.cpp

java 复制代码
int RenderProxy::syncAndDrawFrame() {
    return mDrawFrameTask.drawFrame();
}

这里就进入到本文的主角DrawFrameTask,mDrawFrameTask是在RenderProxy的成员,初始化的时候同步初始化的。 proxy调用mDrawFrameTask.drawFrame来绘制一帧
frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp

java 复制代码
int DrawFrameTask::drawFrame() {
    LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");

    mSyncResult = SyncResult::OK;
    mSyncQueued = systemTime(SYSTEM_TIME_MONOTONIC);
    postAndWait();

    return mSyncResult;
}

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

这里是王RenderThread的任务队列post一个lamda来执行DrawFrameTask的run方法

java 复制代码
void DrawFrameTask::run() {
        TreeInfo info(TreeInfo::MODE_FULL, *mContext);
        canUnblockUiThread = syncFrameState(info);
        canDrawThisFrame = info.out.canDrawThisFrame;
        ....
        nsecs_t dequeueBufferDuration = 0;
        if (CC_LIKELY(canDrawThisFrame)) {
             dequeueBufferDuration = context->draw();
        } else {
            ...
        } 
        ....
}

这里只分析主流程,else部分先省略。这里主要有两个方法

  • syncFrameState 同步帧的状态,这是主要的工作
  • 调用context->draw 绘制内容

本文将分析一下syncFrameState的原理,context->draw的内容在下一篇中继续分析

2 syncFrameState

java 复制代码
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
     ...
    bool canDraw = mContext->makeCurrent();
    ...
    mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
    ...
}

下面主要分析一下这个两个流程

  • mContext->makeCurrent()
  • mContext->prepareTree

2.1 mContext.makeCurrent

mContext是CanvasContext对象,它持有绘制需要的所有依赖。syncFrameState 首先通过调用mContext->makeCurrent()来通知eglManager将mContext 的 EGLSurface对象设置成当前对象,这样在后续EGL绘制就是针对这个当前的EGLSurface。仅仅分析一下OpenGL的情况

frameworks/base/libs/hwui/renderthread/CanvasContext.cpp

java 复制代码
bool CanvasContext::makeCurrent() {
    if (mStopped) return false;

    auto result = mRenderPipeline->makeCurrent();
    switch (result) {
        case MakeCurrentResult::AlreadyCurrent:
            return true;
        case MakeCurrentResult::Failed:
            mHaveNewSurface = true;
            setSurface(nullptr);
            return false;
        case MakeCurrentResult::Succeeded:
            mHaveNewSurface = true;
            return true;
        default:
            LOG_ALWAYS_FATAL("unexpected result %d from IRenderPipeline::makeCurrent",
                             (int32_t)result);
    }
    return true;
}

frameworks/base/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp

java 复制代码
MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
    // TODO: Figure out why this workaround is needed, see b/13913604
    // In the meantime this matches the behavior of GLRenderer, so it is not a regression
    EGLint error = 0;
    if (!mEglManager.makeCurrent(mEglSurface, &error)) {
        return MakeCurrentResult::AlreadyCurrent;
    }
    return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
}

这里通过mEglManager来切换mEglSurface成当前的EGLSurface。 这个mEglSurface是在HardwareRender初始化的时候创建的

2.2 mContext.prepareTree

java 复制代码
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued,
                                RenderNode* target) {
    ...
    for (const sp<RenderNode>& node : mRenderNodes) {
        ...
        info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY);
        node->prepareTree(info);
        GL_CHECKPOINT(MODERATE);
    }
   ...
    info.out.canDrawThisFrame = true;
    
    if (info.out.canDrawThisFrame) {
        int err = mNativeSurface->reserveNext();
        ...
    } 
    ...
}

大部分情况下,mRenderNodes中只有一个元素,就是在初始化时传入的RootRenderNode,这里target的是RootRenderNode,所以info.model 的值将会设置成TreeInfo::MODE_FULL 。从而进入到RootRenderNode的prepareTree方法。在遍历了整个树做好同步之后,

在调用mNativeSurface的reserveNext预定一个GraphicBuffer。

frameworks/base/libs/hwui/RootRenderNode.cpp

java 复制代码
void RootRenderNode::prepareTree(TreeInfo& info) {
   ...
    RenderNode::prepareTree(info);
   ...
}

frameworks/base/libs/hwui/RenderNode.cpp

java 复制代码
void RenderNode::prepareTree(TreeInfo& info) {
    ...
    prepareTreeImpl(observer, info, false);
   ...
}
java 复制代码
void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
    
    if (info.mode == TreeInfo::MODE_FULL) {
        pushStagingPropertiesChanges(info);
    }
    
    if (info.mode == TreeInfo::MODE_FULL) {
        pushStagingDisplayListChanges(observer, info);
    }

    if (mDisplayList) {
        info.out.hasFunctors |= mDisplayList.hasFunctor();
        mHasHolePunches = mDisplayList.hasHolePunches();
        bool isDirty = mDisplayList.prepareListAndChildren(
                observer, info, childFunctorsNeedLayer,
                [this](RenderNode* child, TreeObserver& observer, TreeInfo& info,
                       bool functorsNeedLayer) {
                    child->prepareTreeImpl(observer, info, functorsNeedLayer);
                    mHasHolePunches |= child->hasHolePunches();
                });
        ...
}

pushLayerUpdate(info);

这里主要又有4个逻辑。

  • 同步properties
  • 同步displayList
  • 遍历RenderNode树,对子节点递归调用prepareTreeImpl
  • 如果该RenderNode是的type是RenderLayer的话,为其准备一个用于绘制的SkSurface

2.2.1 同步properties

java 复制代码
void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
   
    if (mDirtyPropertyFields) {
        mDirtyPropertyFields = 0;
        ...
        syncProperties();
        ...
    }
}

接着调用syncProperties,这个方法之前介绍过

java 复制代码
void RenderNode::syncProperties() {
    mProperties = mStagingProperties;
}

2.2.2 同步displayList

java 复制代码
void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {
    if (mNeedsDisplayListSync) {
        mNeedsDisplayListSync = false;
         ...
        syncDisplayList(observer, &info);
        ...
    }
}

接着调用syncDisplayList

java 复制代码
void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {
   ...
    if (mStagingDisplayList) {
        mStagingDisplayList.updateChildren([](RenderNode* child) { child->incParentRefCount(); });
    }
    deleteDisplayList(observer, info);
    mDisplayList = std::move(mStagingDisplayList);
    ...
}

先调用deleteDisplayList把之前的内容清理掉,然后在将mStagingDisplayList的移动到mDisplayList,从而完成同步

java 复制代码
void RenderNode::deleteDisplayList(TreeObserver& observer, TreeInfo* info) {
    if (mDisplayList) {
        mDisplayList.updateChildren(
                [&observer, info](RenderNode* child) { child->decParentRefCount(observer, info); });
        mDisplayList.clear(this);
    }
}

2.2.3 遍历子树

java 复制代码
bool SkiaDisplayList::prepareListAndChildren(
        TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
        std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) {
    ...
    for (auto& child : mChildNodes) {
        RenderNode* childNode = child.getRenderNode();
        ...
        childFn(childNode, observer, info, functorsNeedLayer);
        ...
    }
    ...
}

遍历mChildNodes,它是一个RenderNodeDrawable的dequeue, 之前分析DisplayList的时候介绍个类,它包装了一个RenderNode,针对每个RenderNodeDrawable持有的RenderNode,调用childFn来处理。这是外部传来的回调函数

java 复制代码
[this](RenderNode* child, TreeObserver& observer, TreeInfo& info,
                       bool functorsNeedLayer) {
                    child->prepareTreeImpl(observer, info, functorsNeedLayer);

于是递归调用child->prepareTreeImpl(observer, info, functorsNeedLayer);方法,对整棵树进行遍历。

2.2.4 pushLayerUpdate

如果当前RenderNode的type是RenderLayer 且有内容可以绘制,会为当前的RenderNode生成一个使用GPU绘制的SkSurface,并保存到RenderNode的setLayerSurface,之后将RenderNode和它对应的damage保存到TreeInfo的layerUpdateQueue。如果damage为空的话,表示没有变化的区域。这些数将将在后面的绘制用使用。 Layer的作用是将一些绘制指令固定下来到一个纹理,这样可以减少同一段指令的执行次数。在绘制这个RenderNode的是时候,直接绘制这个纹理。这适合那些不经常变化的节点,但是大部分默认的View或者RenderNode的type并不是RenderLayer,而是LAYER_TYPE_NONE。当我们确信某个View的属性不会发生变化的时候,可以通过调用View.setLayerType(LAYER_TYPE_HARDWARE,paint)的方式让底层为其准备一个LayerSurface。这正是pushLayerUpdate对应的逻辑

java 复制代码
void RenderNode::pushLayerUpdate(TreeInfo& info) {
     LayerType layerType = properties().effectiveLayerType();
     if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable()) ||
        CC_UNLIKELY(properties().getWidth() == 0) || CC_UNLIKELY(properties().getHeight() == 0) ||
        CC_UNLIKELY(!properties().fitsOnLayer())) {
        if (CC_UNLIKELY(hasLayer())) {
            this->setLayerSurface(nullptr);
        }
        return;
    }
    if (info.canvasContext.createOrUpdateLayer(this, *info.damageAccumulator, info.errorHandler)) {
        damageSelf(info);
    }

    if (!hasLayer()) {
        return;
    }

    SkRect dirty;
    info.damageAccumulator->peekAtDirty(&dirty);
    info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty);
    if (!dirty.isEmpty()) {
      mStretchMask.markDirty();
    }

    // There might be prefetched layers that need to be accounted for.
    // That might be us, so tell CanvasContext that this layer is in the
    // tree and should not be destroyed.
    info.canvasContext.markLayerInUse(this);
}

frameworks/base/libs/hwui/renderthread/CanvasContext.h

arduino 复制代码
bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& dmgAccumulator,
                             ErrorHandler* errorHandler) {
        return mRenderPipeline->createOrUpdateLayer(node, dmgAccumulator, errorHandler);
    }

frameworks/base/libs/hwui/pipeline/skia/SkiaPipeline.cpp

java 复制代码
bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
                                       ErrorHandler* errorHandler) {
    // compute the size of the surface (i.e. texture) to be allocated for this layer
    const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
    const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;

    SkSurface* layer = node->getLayerSurface();
    if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
        SkImageInfo info;
        info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
                                 kPremul_SkAlphaType, getSurfaceColorSpace());
        SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
        SkASSERT(mRenderThread.getGrContext() != nullptr);
        node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
                                                          SkBudgeted::kYes, info, 0,
                                                          this->getSurfaceOrigin(), &props));
        ...
        return true;
    }
    return false;
}

根据node的尺寸生成一个SkImageInfo,然后调用MakeRenderTarget再生成一个SkSurface,MakeRenderTarget方法将返回一个在GPU完成绘制的surface。然后将该surface作为参数,调用node->setLayerSurface方法设置到RenderNode中去。最后在调用enqueueLayerWithDamage将RenderNode保存到LayerUpdateQueue的mEntries中( 在绘制的时候会先绘制有更新的Layer)

frameworks/base/libs/hwui/LayerUpdateQueue.cpp

java 复制代码
void LayerUpdateQueue::enqueueLayerWithDamage(RenderNode* renderNode, Rect damage) {
    damage.roundOut();
    damage.doIntersect(0, 0, renderNode->getWidth(), renderNode->getHeight());
    if (!damage.isEmpty()) {
        for (Entry& entry : mEntries) {
            if (CC_UNLIKELY(entry.renderNode == renderNode)) {
                entry.damage.unionWith(damage);
                return;
            }
        }
        mEntries.emplace_back(renderNode, damage);
    }
}

damage为空会并被忽略。

2.3 mNativeSurface->reserveNext

在遍历完RendNode树之后,会调用mNativeSurface去申请GraphicBuffer,mNativeSurface的类型是ReliableSurface,它是对ANativeWindow的封装,即对Surface的封装,所以最后还是通过Surface去申请的

java 复制代码
int ReliableSurface::reserveNext() {
    int fenceFd = -1;
    ANativeWindowBuffer* buffer = nullptr;

    // Note that this calls back into our own hooked method.
    int result = ANativeWindow_dequeueBuffer(mWindow, &buffer, &fenceFd);

    {
        std::lock_guard _lock{mMutex};
        LOG_ALWAYS_FATAL_IF(mReservedBuffer, "race condition in reserveNext");
        mReservedBuffer = buffer;
        mReservedFenceFd.reset(fenceFd);
    }

    return result;
}

这里ANativeWindowBuffer是一个结构体,它是GraphicBuffer的父类。

frameworks/native/libs/nativewindow/include/android/hardware_buffer.h

java 复制代码
typedef struct AHardwareBuffer AHardwareBuffer;

frameworks/native/libs/ui/include/ui/GraphicBuffer.h

java 复制代码
class GraphicBuffer
    : public ANativeObjectBase<ANativeWindowBuffer, GraphicBuffer, RefBase>,
      public Flattenable<GraphicBuffer>
{
    friend class Flattenable<GraphicBuffer>;

通过这个全局函数ANativeWindow_dequeueBuffer,会调用到window的dequeueBuffer方法,经过Surface拦截后,调用到Surface的dequeBuffer,然后Surface再调用它的producer去dequeueBuffer,完成buffer的申请。因此reserveNext真正触发申请graphicbuffer的地方

3 总结

这一篇文章我们开始进入到绘制帧的流程分析,从Java的draw方法开始,进行一帧的和绘制,会通过C层的RenderProxy代理后,转发到RenderContext的draw方法,在转发到DrawFrameTask.drawFrame方法,它会通过王RenderThread的queue创建一个任务,于是进入到RenderThread这个子线程里去执行绘制逻辑。 绘制时:

  • 首先将调用EGLManager.makeCurrent()将当前的EGLSurface作为OpenGL的绘制表面,
  • 然后同步RenderNode树形结构的属性和displayList,并为type为RENDER_LAYER的RenderNode创建使用GPU绘制的SkSurface。
  • 最后调用mNativeSurface.reserveNext 准备GraphicBuffer。

到这里,绘制需要的资源,需要绘制的内容都准备就绪,接下来就是开始执行绘制动作context->draw(),这将在下一篇中继续分析

👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀

相关推荐
潘潘潘17 分钟前
Android多线程机制简介
android
CYRUS_STUDIO2 小时前
利用 Linux 信号机制(SIGTRAP)实现 Android 下的反调试
android·安全·逆向
CYRUS_STUDIO2 小时前
Android 反调试攻防实战:多重检测手段解析与内核级绕过方案
android·操作系统·逆向
黄林晴6 小时前
如何判断手机是否是纯血鸿蒙系统
android
火柴就是我6 小时前
flutter 之真手势冲突处理
android·flutter
法的空间6 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
循环不息优化不止7 小时前
深入解析安卓 Handle 机制
android
恋猫de小郭7 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
jctech7 小时前
这才是2025年的插件化!ComboLite 2.0:为Compose开发者带来极致“爽”感
android·开源
用户2018792831677 小时前
为何Handler的postDelayed不适合精准定时任务?
android