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老皮!!!欢迎大家来找我探讨交流👀

相关推荐
Winston Wood2 小时前
Android Parcelable和Serializable的区别与联系
android·序列化
清风徐来辽2 小时前
Android 项目模型配置管理
android
帅得不敢出门2 小时前
Gradle命令编译Android Studio工程项目并签名
android·ide·android studio·gradlew
problc3 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
帅得不敢出门13 小时前
安卓设备adb执行AT指令控制电话卡
android·adb·sim卡·at指令·电话卡
我又来搬代码了15 小时前
【Android】使用productFlavors构建多个变体
android
德育处主任17 小时前
Mac和安卓手机互传文件(ADB)
android·macos
芦半山17 小时前
Android“引用们”的底层原理
android·java
迃-幵17 小时前
力扣:225 用队列实现栈
android·javascript·leetcode
大风起兮云飞扬丶18 小时前
Android——从相机/相册获取图片
android