美文网首页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 绘制DisplayList

    在连续写了Android DisplayList 构建过程和Android 同步DisplayList信息后,接下...

  • Android-View绘制

    View绘制分三个步骤,顺序是:onMeasure,onLayout,onDraw。 调用invalidate方法...

  • Android - View 的工作原理

    Android-View 的工作原理 View 的工作流程主要指的是 measure、layout、draw 这三...

  • Android-View 绘制流程

    前言 距离上次发文大约已经过了十个月左右,期间换了份工作,有着不错的领导跟同事,还多了一位可爱至极的赵老师陪着我。...

  • Android-View的绘制

    View的绘制过程android程序启动--->Activity加载--->View的绘制 1、前期调用 2、调用...

  • Android-View绘制流程

    View树的绘制流程 当Activity接收到焦点的时候,它会被请求绘制布局,该请求由Android framew...

  • Android-View绘制流程浅析

    引 这段时间学习了下View的绘制流程,本着好记性不如烂笔头的原则,尝试将这些内容记录下来,用于巩固和总结。这次学...

  • UIView绘制原理&异步绘制

    绘制原理 异步绘制流程

  • 常见面试问题概括

    UI视图相关 *TableView重用机制? 答: *视图绘制原理?如何实现异步绘制? 答:UIView绘制原理 ...

  • Android-View的绘制流程解析

    View的绘制流程概述 Window的创建:Activity启动时最终会调用ActivityThread.perf...

网友评论

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

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