美文网首页Android开发一路向下之AOSP研究
Android-View绘制原理(08)-DisplayList

Android-View绘制原理(08)-DisplayList

作者: 代码多哥 | 来源:发表于2023-07-25 18:30 被阅读0次

    DisplayList是一个神秘的存在,因为它在Java层没有对应的实体,但是却出现在了View的绘制流程了。在前面的文章中也提到过这个类。在View的绘制流程中,有一个很重要的方法updateDisplayListIfDirty,但是单纯只从Jave层看,却很难理解它做了什么

    public RenderNode updateDisplayListIfDirty() {
            final RenderNode renderNode = mRenderNode;
            if (!canHaveDisplayList()) {
                // can't populate RenderNode, don't try
                return renderNode;
            }
    
            if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
                    || !renderNode.hasDisplayList()
                    || (mRecreateDisplayList)) {
                // Don't need to recreate the display list, just need to tell our
                // children to restore/recreate theirs
                if (renderNode.hasDisplayList()
                        && !mRecreateDisplayList) {
                    mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchGetDisplayList();
    
                    return renderNode; // no work needed
                }
    
                // If we got here, we're recreating it. Mark it as such to ensure that
                // we copy in child display lists into ours in drawChild()
                mRecreateDisplayList = true;
    
                int width = mRight - mLeft;
                int height = mBottom - mTop;
                int layerType = getLayerType();
    
                // Hacky hack: Reset any stretch effects as those are applied during the draw pass
                // instead of being "stateful" like other RenderNode properties
                renderNode.clearStretch();
    
                final RecordingCanvas canvas = renderNode.beginRecording(width, height);
    
                try {
                    if (layerType == LAYER_TYPE_SOFTWARE) {
                        buildDrawingCache(true);
                        Bitmap cache = getDrawingCache(true);
                        if (cache != null) {
                            canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                        }
                    } else {
                        computeScroll();
    
                        canvas.translate(-mScrollX, -mScrollY);
                        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    
                        // Fast path for layouts with no backgrounds
                        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                            dispatchDraw(canvas);
                            drawAutofilledHighlight(canvas);
                            if (mOverlay != null && !mOverlay.isEmpty()) {
                                mOverlay.getOverlayView().draw(canvas);
                            }
                            if (isShowingLayoutBounds()) {
                                debugDrawFocus(canvas);
                            }
                        } else {
                            draw(canvas);
                        }
                    }
                } finally {
                    renderNode.endRecording();
                    setDisplayListProperties(renderNode);
                }
            } else {
                mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            }
            return renderNode;
        }
    

    这个方法是在Java中为数不多的涉及到DisplayList地方,但是让我们难以理解它是如何更新? 因为整个流程中看不到修改的代码,也没有DisplayList身影,我们能够明白的是,在一系列条件满足后,mRecreateDisplayList 会被设置为true, 然后开始了一次canvas绘制操作。 没错,canvas绘制就是在更新DisplayList,只不过这所有的事情都在C层完成了,所以在Java层没有体现。但是即便进入到C层,我们会发现它也是一个很神秘的存在。这篇文章将会揭示关于DisplayList那些隐晦的逻辑。

    1 定义

    在C层关于DisplayList又两个头文件
    frameworks/base/libs/hwui/DisplayList.h
    frameworks/base/libs/hwui/pipeline/skia/SkiaDisplayList.h
    在分析RenderCanvas那篇文章中,我们也提到到过SkiaDisplayList.也简单分析了它的绘制逻辑。但是它更DisplayList是什么关系呢?是继承关系吗?我们从源码来来分析一下
    frameworks/base/libs/hwui/DisplayList.h

    using DisplayList = SkiaDisplayListWrapper;
    

    从源码中可以看到,DisplayList原来只是SkiaDisplayListWrapper的别称,因此我们看看SkiaDisplayListWrapper

    class SkiaDisplayListWrapper {
    public:
        // Constructs an empty (invalid) DisplayList
        explicit SkiaDisplayListWrapper() {}
    
        // Constructs a DisplayList from a SkiaDisplayList
        explicit SkiaDisplayListWrapper(std::unique_ptr<skiapipeline::SkiaDisplayList> impl)
            : mImpl(std::move(impl)) {}
            ...
      }
    

    这是一个wrapper包装类,它包装的是一个SkiaDisplayList类型的mImpl对象,也就是说业务层其实并不会直接使用SkiaDisplayList,而是会使用包装器对象,但是起作用的其实还是mImpl这个SkiaDisplayList对象。我看一个简单的包装逻辑

           [[nodiscard]] bool hasContent() const {
                  return mImpl && !(mImpl->isEmpty());
          }
    

    可以看到,wrapper方法内部都是在调用mImpl的方法。
    SkiaDisplayListWrapper 也提供值将内部的mImpl转换成SkiaDisplayList方法

       [[nodiscard]] skiapipeline::SkiaDisplayList* asSkiaDl() {
            return mImpl.get();
        }
    
        [[nodiscard]] const skiapipeline::SkiaDisplayList* asSkiaDl() const {
            return mImpl.get();
        }
    

    因此我们在代码中可能更多的看到DisplayList这个包装者,而不是SkiaDisplayList。

    2 DisplayList的角色

    DisplayList在整个UI框架中起的是作用可以概括位2个,

    • 是维持绘制的树形结构。在RendNode中,我们说它也是一个树形的结构,但是并没有看到相对应的定义,比如children字段,因为这个树状结构是定义在DisplayList中的。
    • 保存绘制指令。在SkiaDisplayList中有一个mDisplayList的DisplayListData类型的成员,所有的绘制指令保存在这个对象的fBytes二进制数据中。这个将在介绍RecordingCanvas的文章中进行分析

    本文主要分析分析一下树形结构。
    frameworks/base/libs/hwui/pipeline/skia/SkiaDisplayList.h

    class SkiaDisplayList {
         ...
         std::deque<RenderNodeDrawable> mChildNodes;
         ...
    }
    

    在定义中定义了一个mChildNodes的deque队列,元素类型是RenderNodeDrawable, 这是一个新类,它封装了一个RenderNode,使得它可以通过skia来绘制,简单的来说它持有一个RenderNode,然后可以被绘制。

    RenderNodeDrawable::RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer,
                                           bool inReorderingSection)
            : mRenderNode(node)
            , mRecordedTransform(canvas->getTotalMatrix())
            , mComposeLayer(composeLayer)
            , mInReorderingSection(inReorderingSection) {}
    

    它的绘制流程我们先不分析,仅仅需要知道mChildNodes其实就相当于一个RenderNode的队列,从而一个形成了树形的结构。

    RenderNode
             -DisplayList
                    -mChildNodes[0]:  RenderNodeDrawable
                                - RendeNode
                    -mChildNodes[1]:  RenderNodeDrawable
                                - RendeNode
                    ....
                          
    

    3 树形结构的建立流程

    这个一节来分析一下这棵树的建立过程

    3.1 Root

    在C层,有一个专门充当Root的RenderNode,那就是RootRenderNode,它继承自RenderNode, 它是在绘制初始化的时同步初始化的,并由Java层的HardwareRender持有。

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

     public HardwareRenderer() {
            ProcessInitializer.sInstance.initUsingContext();
            mRootNode = RenderNode.adopt(nCreateRootRenderNode());
            mRootNode.setClipToBounds(false);
            mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode);
            if (mNativeProxy == 0) {
                throw new OutOfMemoryError("Unable to create hardware renderer");
            }
            Cleaner.create(this, new DestroyContextRunnable(mNativeProxy));
            ProcessInitializer.sInstance.init(mNativeProxy);
        }
    

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

    static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) {
        RootRenderNode* node = new RootRenderNode(std::make_unique<JvmErrorReporter>(env));
        node->incStrong(0);
        node->setName("RootRenderNode");
        return reinterpret_cast<jlong>(node);
    }
    
    class RootRenderNode : public RenderNode {
    public:
        explicit RootRenderNode(std::unique_ptr<ErrorHandler> errorHandler)
                : RenderNode(), mErrorHandler(std::move(errorHandler)) {}
     ...
    }
    

    3.1 添加节点

    绘制树是在绘制的时候创建,因此只有ViewGroup才有子控件,所以添加节点是在ViewGroup绘制的时候发生的,在更新updateDisplayListIfDirty的时候,如果需要重建它displayList的时候,会通过的RenderNode重新开始记录
    frameworks/base/core/java/android/view/View.java

    public RenderNode updateDisplayListIfDirty() {
                final RenderNode renderNode = mRenderNode;
                ...
                final RecordingCanvas canvas = renderNode.beginRecording(width, height);
                 ....
                 dispatchDraw(canvas);
                 ....          
                 renderNode.endRecording();
                 setDisplayListProperties(renderNode);
                  ...
                 return renderNode;
        }
    
    

    此处假设该ViewGroup的添加了某个子控件,于是调用了dispatchDraw方法,将child的变更记录到自己的renderNode的创建的RecrodingCanvas的dislplayList的mChildNodes之后,返回更新后的renderNode,供上一级的ViewGroup作相同的处理。

    frameworks/base/core/java/android/view/ViewGroup.java

     protected void dispatchDraw(Canvas canvas) {
            ...
            for (int i = 0; i < childrenCount; i++) {
                while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                    final View transientChild = mTransientViews.get(transientIndex);
                    if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                            transientChild.getAnimation() != null) {
                        more |= drawChild(canvas, transientChild, drawingTime);
                    }
                    transientIndex++;
                    if (transientIndex >= transientCount) {
                        transientIndex = -1;
                    }
                }
                ...
       }
    

    循环调用drawChild方法,传入的canvas是RecrodingCanvas

    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
            return child.draw(canvas, this, drawingTime);
        }
    

    这里就进入非常重要的View.draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法
    frameworks/base/core/java/android/view/View.java

     boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
            ....
            if (!drawingWithDrawingCache) {
                if (drawingWithRenderNode) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    ((RecordingCanvas) canvas).drawRenderNode(renderNode);
                } else {
                   ....
                }
                    
            return more;
        }
    

    这里是通过把child的renderNode直接使用drawRenderNode方法画出来,它会进入到jni的方法
    frameworks/base/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp

     {"nDrawRenderNode", "(JJ)V", (void*)android_view_DisplayListCanvas_drawRenderNode},
    
    static void android_view_DisplayListCanvas_drawRenderNode(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jlong renderNodePtr) {
        Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
        RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
        canvas->drawRenderNode(renderNode);
    }
    
    

    这个Canvas的类型是SkiaRecordingCanvas

    void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) {
        // Record the child node. Drawable dtor will be invoked when mChildNodes deque is cleared.
        mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);
        ....
    }
    

    在这里我们看到了drawRenderNode方法会先把这个renderNode 构造成一个RenderNodeDrawable,(RenderNodeDrawable的构造方法在上面已经介绍过了)然后放入到mChildNodes,从而添加到了RenderNode树添加上。

    4 总结

    本文主要介绍了DisplayList这个神秘的存在以及它的作用

    • 记录绘制命令Op - DisplayListData
    • 保存绘制树形结构 - mChildNodes

    然后着重分析了一下RenderNode树的创建过程,它是ViewGroup在绘制子控件时通过drawRenderNode时将子控件的RenderNode添加到树中的。在Java层重新绘制的,只需要去调用updateDisplayListIfDirty就可以, 因为变更都保存到底层的这棵树了。

    相关文章

      网友评论

        本文标题:Android-View绘制原理(08)-DisplayList

        本文链接:https://www.haomeiwen.com/subject/kgyhpdtx.html