【硬件加速】2、DisplayList构建过程分析【Android 13】

在这里插入图片描述

在硬件加速渲染环境中,Android应用程序窗口的UI渲染是分两步进行的。第一步是构建DisplayList,发生在应用程序进程的Main Thread中;第二步是渲染DisplayList,发生在应用程序进程的Render Thread中。DisplayList是以View为单位进行构建的,因此每一个View都对应有一个DisplayList。

这里说的DisplayList与Open GL里面的DisplayList在概念上是类似的,不过是两个不同的实现。DisplayList的本质是一个缓冲区,它里面记录了即将要执行的绘制命令序列。这些绘制命令最终会转化为Open GL命令由GPU执行。这意味着我们在调用Canvas API绘制UI时,实际上只是将Canvas API调用及其参数记录在DisplayList中,然后等到下一个Vsync信号到来时,记录在DisplayList里面的绘制命令才会转化为Open GL命令由GPU执行。与直接执行绘制命令相比,先将绘制命令记录在DisplayList中然后再执行有两个好处。第一个好处是在绘制窗口的下一帧时,若某一个VIew的UI没有发生变化,那么就不必执行与它相关的Canvas API,即不用执行它的成员函数onDraw,而是直接复用上次构建的DisplayList即可。第二个好处是在绘制窗口的下一帧时,若某一个VIew的UI发生了变化,但是只是一些简单属性发生了变化,例如位置和透明度等简单属性,那么也不必重建它的DisplayList,而是直接修改上次构建的DisplayList的相关属性即可,这样也可以省去执行它的成员函数onDraw。

窗口的View层级结构是树形结构,那么DisplayList的层级结构自然也是一个以View的树形结构建立起来的Display树形结构,如图所示:

在这里插入图片描述

要知道DisplayList的信息并不是直接保存在View中的,Android抽象出了一个RenderNode的概念来和View树中的每一个View一一对应,并保存DisplayList的信息,顾名思义,每一个RenderNode代表了树形结构中的一个节点。

为什么要设计这么一个类出来呢,因为View毕竟只是Framework Java层的概念,在Framework C/C++层是没有View这个概念的,但是底层仍需要一个结构体来保存从上层传来的View信息,比如几何信息和绘制信息等,而保存这些信息的就是RenderNode。

接下来我们就结合源代码来分析Android应用程序窗口视图的DisplayList的构建过程。

Android应用程序窗口UI的绘制过程是从ViewRootImpl类的成员函数draw开始的,它的实现如下所示:

// frameworks\base\core\java\android\view\ViewRootImpl.java    

	private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
   
   
        // ......
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
   
   
            if (isHardwareEnabled()) {
   
   
                // ......
                
                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
            } else {
   
   
                // ......
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
   
   
                    if (DEBUG_DRAW) {
   
   
                        Log.v(mTag, "drawSoftware return: this = " + this);
                    }
                    return false;
                }
            }
        }

        // ......
    }

在几种情况下,需要进行绘制:

1)、当前需要更新的区域,即ViewRootImpl类的成员变量mDirty描述的脏区域不为空。

2)、窗口当前有动画需要执行,即ViewRootImpl类的成员变量mIsAnimating的值等于true。

3)、accessibilityFocusDirty,无障碍服务相关。

之前已经已经分析过了,当硬件加速是默认开启的,所以这里走的就是硬件绘制流程ThreadedRenderer.draw,而非软件绘制drawSoftware。

1 ThreadedRenderer.draw

// frameworks\base\core\java\android\view\ThreadedRenderer.java

	void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
   
   
        // ......

        updateRootDisplayList(view, callbacks);

        // ......

        int syncResult = syncAndDrawFrame(frameInfo);
        // ......
    }

1)、ThreadedRenderer.updateRootDisplayList函数以传参View为顶层View构建DisplayList树,对于Activity来说顶层View为DecorView。

2)、ThreadedRenderer.syncAndDrawFrame用来绘制新的一帧。

本文分析DisplayList树的构建流程。

2 ThreadedRenderer.updateRootDisplayList

// frameworks\base\core\java\android\view\ThreadedRenderer.java

	private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
   
   
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
        updateViewTreeDisplayList(view);

        // ......

        if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
   
   
            RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
            try {
   
   
                // ......

                canvas.enableZ();
                canvas.drawRenderNode(view.updateDisplayListIfDirty());
                canvas.disableZ();

                // ......
            } finally {
   
   
                mRootNode.endRecording();
            }
        }
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }

1)、ThreadedRenderer类的成员函数updateRootDisplayList通过调用另一个成员函数updateViewTreeDisplayList来构建参数view描述的视图的Display List,即图1中的Decor View的Display List。

2)、mRootNodeNeedsUpdate表示是否需要更新另外一个成员变量mRootNode描述的一个Render Node的Display List。

3)、RenderNode.hasDisplayList返回RenderNode是否有Display List,如果返回 false,RenderNode 应该使用 RenderNode.beginRecording 和RenderNode.endRecording 重新记录。没有显示列表的 RenderNode 仍然可以被绘制,但是在其显示列表被更新之前,它不会对渲染内容产生影响。当 RenderNode 不再被任何东西绘制时,系统可能会自动调用 {@link #discardDisplayList()}。 因此,在绘制之前确保 RenderNode 上的 hasDisplayList 为真非常重要。

4)、ThreadedRenderer类的成员变量mRootNode描述的Render Node即为当前窗口的Root Node,更新它的Display List实际上就是要将参数view描述的视图的Display List记录到它里面去,具体方法如下所示:

  1. 开始记录之前,调用RenderNode.beginRecording,该方法返回一个RecordingCanvas类型的Canvas,在这个Canvas上执行的所有操作都会被记录并且存储在DisplayList上。
  2. 调用上面获得的RecordingCanvas的成员函数drawRenderNode将参数view描述的视图的Display List绘制在它里面。
  3. 记录结束后,调用RenderNode.endRecording,取出上述已经绘制好的RecordingCanvas的数据,并且作为上述Render Node的新的Display List。

3 保存绘制命令到RenderNode

3.1 ThreadedRenderer.updateViewTreeDisplayList

// frameworks\base\core\java\android\view\ThreadedRenderer.java

	private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
   
   
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
        updateViewTreeDisplayList(view);

        // ......
    }

    private void updateViewTreeDisplayList(View view) {
   
   
        view.mPrivateFlags |= View.PFLAG_DRAWN;
        view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
                == View.PFLAG_INVALIDATED;
        view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
        view.updateDisplayListIfDirty();
        view.mRecreateDisplayList = false;
    }

3.2 View.updateDisplayListIfDirty

    /**
     * Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)
     * @hide
     */
    @NonNull
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public RenderNode updateDisplayListIfDirty() {
   
   
        final RenderNode renderNode = mRenderNode;
        // ......

        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) {
   
   
                // ......
                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();

            // ......
            
            final RecordingCanvas canvas = renderNode.beginRecording(width, height);

            try {
   
   
                if (layerType == LAYER_TYPE_SOFTWARE) {
   
   
                    // ......
                } else {
   
   
                    // ......
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
   
   
                        dispatchDraw(canvas);
                        // ......
                    } else {
   
   
                        draw(canvas);
                    }
                }
            } finally {
   
   
                renderNode.endRecording();
                setDisplayListProperties(renderNode);
            }
            // ......
        } else {
   
   
            // ......
        }
        // ......
    }

在三种情况下,View类的成员函数updateDisplayListIfDirty需要重新构建当前正在处理的View或者其子View的Display List:

1)、View类的成员变量mPrivateFlags的值的PFLAG_DRAWING_CACHE_VALID位等于0,这表明上次构建的Display List已经失效。

2)、RenderNode.hasDisplayList函数返回false,表示当前RenderNode没有一个DisplayList,需要重新记录。

3)、VIew.mRecreateDisplayList为true,表示当前View被标记为INVALIDATED,或者其DisplayList已无效,此时必须重绘其DisplayList。

其中,如果View类的成员变量mPrivateFlags的值的PFLAG_DRAWING_CACHE_VALID位不等于0,并且成员变量mRenderNode描述的Render Node内部维护的Display List Data也是有效的,那么就表明上次为当前正在处理的View的UI没有发生变化。但是如果在这种情况下,View类的成员变量mRecreateDisplayList等于false,就说明虽然当前正在处理的View的UI没有发生变化,但是它的子View的UI发生了变化。这时候就需要对这些子View的Display List进行重新构建,并且更新到当前正在处理的View的Display List去。这是通过调用View类的成员函数dispatchGetDisplayList来完成的。

除了上述这种情况,其余情况均表明需要重新构建当前正在处理的View及其子View的Display List。这些Display List的构建过程如下所示:

1)、从当前正在处理的View关联的Render Node获得一个RecordingCanvas。

2)、将当前正在处理的View及其子View的UI绘制命令记录在上面获得的RecordingCanvas中。

3)、将前面已经绘制好的RecordingCanvas的Display List Data提取出来,并且设置为当前正在处理的View关联的Render Node里面去。

接下来会依次分析这三个步骤,但是在此之前,先看下这里View的成员变量mRenderNode。

View.mRenderNode的定义为:

    /**
     * RenderNode holding View properties, potentially holding a DisplayList of View content.
     * <p>
     * When non-null and valid, this is expected to contain an up-to-date copy
     * of the View content. Its DisplayList content is cleared on temporary detach and reset on
     * cleanup.
     */
    @UnsupportedAppUsage
    final RenderNode mRenderNode;

RenderNode持有View的属性,潜在地持有View内容的DisplayList。

当RenderNode为非空且有效,它被期望包含View内容的最新副本。它的DisplayList内容会在该View暂时从Window上分离的时候被清除,并且在View被清理的时候重置。

在View的构造方法中赋值:

    public View(Context context) {
   
   
        // ......
        
        mRenderNode = RenderNode.create(getClass().getName(), new ViewAnimationHostBridge(this));

        // ......
    }

3.2.1 RenderNode.create

    private RenderNode
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值