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

1,027 阅读35分钟

在硬件加速渲染环境中,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示意图.png

要知道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(String name, AnimationHost animationHost) {
        mNativeRenderNode = nCreate(name);
        // ......
    }

    /** @hide */
    public static RenderNode create(String name, @Nullable AnimationHost animationHost) {
        return new RenderNode(name, animationHost);
    }

调用JNI函数创建一个Nativce层的RenderNode对象,并将其地址保存在成员变量mNativeRenderNode中。

nCreate定义为:

// frameworks\base\libs\hwui\jni\android_graphics_RenderNode.cpp

static const JNINativeMethod gMethods[] = {
        // ----------------------------------------------------------------------------
        // Regular JNI
        // ----------------------------------------------------------------------------
        {"nCreate", "(Ljava/lang/String;)J", (void*)android_view_RenderNode_create},
    	// ......
}

那么调用的就是android_view_RenderNode_create函数。

3.2.2 android_view_RenderNode_create

static jlong android_view_RenderNode_create(JNIEnv* env, jobject, jstring name) {
    RenderNode* renderNode = new RenderNode();
    
    // ......
    
    return reinterpret_cast<jlong>(renderNode);
}

在Nativce层创建一个RenderNode对象,并返回其地址。

补充:RenderNode定义

这里翻译一下源码中RenderNode的定义。

RenderNode is used to build hardware accelerated rendering hierarchies. Each RenderNode contains both a display list as well as a set of properties that affect the rendering of the display list. RenderNodes are used internally for all Views by default and are not typically used directly.

RenderNode用来建立硬件加速渲染View层级结构。每一个RenderNode包含了一个DisplayList,以及一个影响该DisplayList渲染的属性集合。默认情况下,RenderNodes 在内部用于所有视图,通常不会直接使用。

RenderNodes are used to divide up the rendering content of a complex scene into smaller pieces that can then be updated individually more cheaply. Updating part of the scene only needs to update the display list or properties of a small number of RenderNode instead of redrawing everything from scratch. A RenderNode only needs its display list re-recorded when its content alone should be changed. RenderNodes can also be transformed without re-recording the display list through the transform properties.

RenderNode用于将复杂场景的渲染内容分成更小的部分,然后可以用更小的性能损耗来单独更新这些部分。更新场景的某部分只需要更新对应的DisplayList或者一小部分RenderNode的属性,而不用从头全部重绘。当一个RenderNode的内容被单独改变时,它只需要将其DisplayList重新进行记录。RenderNode也能在不重新记录DisplayList的情况下通过变换属性进行变换。

A text editor might for instance store each paragraph into its own RenderNode. Thus when the user inserts or removes characters, only the display list of the affected paragraph needs to be recorded again.

例如,文本编辑器可以将每个段落存储到它自己的RenderNode中。当用户插入或删除字符时,只需要重新记录受影响段落的显示列表。

Hardware acceleration

RenderNodes can be drawn using a {@link RecordingCanvas}. They are not supported in software. Always make sure that the {@link android.graphics.Canvas} you are using to render a display list is hardware accelerated using {@link android.graphics.Canvas#isHardwareAccelerated()}.

可以使用RecordingCanvas 来绘制RenderNode。它们不支持通过软件绘制。始终确保你用来渲染一个DisplayList的android.graphics.Canvas 使用android.graphics.Canvas#isHardwareAccelerated() 进行硬件加速。

Creating a RenderNode

      RenderNode renderNode = new RenderNode("myRenderNode");
      renderNode.setPosition(0, 0, 50, 50); // Set the size to 50x50
      RecordingCanvas canvas = renderNode.beginRecording();
      try {
          // Draw with the canvas
          canvas.drawRect(...);
      } finally {
          renderNode.endRecording();
      }

Drawing a RenderNode in a View

     protected void onDraw(Canvas canvas) {
         if (canvas.isHardwareAccelerated()) {
             // Check that the RenderNode has a display list, re-recording it if it does not.
             if (!myRenderNode.hasDisplayList()) {
                 updateDisplayList(myRenderNode);
             }
             // Draw the RenderNode into this canvas.
             canvas.drawRenderNode(myRenderNode);
         }
     }

Releasing resources

This step is not mandatory but recommended if you want to release resources held by a display list as soon as possible. Most significantly any bitmaps it may contain.

如果你想尽快释放被DisplayList持有的资源,建议执行此步骤(非强制)。

    // Discards the display list content allowing for any held resources to be released.
    // After calling this
    renderNode.discardDisplayList();

Properties

In addition, a RenderNode offers several properties, such as {@link #setScaleX(float)} or {@link #setTranslationX(float)}, that can be used to affect all the drawing commands recorded within. For instance, these properties can be used to move around a large number of images without re-issuing all the individual canvas.drawBitmap() calls.

此外,RenderNode还提供了一些属性,如#setScaleX(float)#setTranslationX(float) ,这些属性能够用来影响记录在其中的所有绘制指令。比如,这些属性能够移动大量的图片,而无需重新发起所有单独的canvas.drawBitmap() 调用。

    private void createDisplayList() {
        mRenderNode = new RenderNode("MyRenderNode");
        mRenderNode.setPosition(0, 0, width, height);
        RecordingCanvas canvas = mRenderNode.beginRecording();
        try {
            for (Bitmap b : mBitmaps) {
                canvas.drawBitmap(b, 0.0f, 0.0f, null);
                canvas.translate(0.0f, b.getHeight());
            }
        } finally {
            mRenderNode.endRecording();
        }
    }

    protected void onDraw(Canvas canvas) {
        if (canvas.isHardwareAccelerated())
            canvas.drawRenderNode(mRenderNode);
        }
    }

    private void moveContentBy(int x) {
         // This will move all the bitmaps recorded inside the display list
         // by x pixels to the right and redraw this view. All the commands
         // recorded in createDisplayList() won't be re-issued, only onDraw()
         // will be invoked and will execute very quickly
         mRenderNode.offsetLeftAndRight(x);
         invalidate();
    }

A few of the properties may at first appear redundant, such as {@link #setElevation(float)} and {@link #setTranslationZ(float)}. The reason for these duplicates are to allow for a separation between static & transient usages. For example consider a button that raises from 2dp to 8dp when pressed. To achieve that an application may decide to setElevation(2dip), and then on press to animate setTranslationZ to 6dip. Combined this achieves the final desired 8dip value, but the animation need only concern itself with animating the lift from press without needing to know the initial starting value. {@link #setTranslationX(float)} and {@link #setTranslationY(float)} are similarly provided for animation uses despite the functional overlap with {@link #setPosition(Rect)}.

一些属性可能最初的时候出现的有一些冗余,如#setElevation(float)#setTranslationZ(float) 。 这些重复的原因是允许静态和瞬态用法之间的分隔。比如,考虑一个按钮,在按下时从2dp上升到8dp。为了实现这一点,App可能决定去setElevation(2dip),按钮按下时去动画,setTranslationZ到6dip。两者结合最终实现了8dip的值,但是动画应只关心其本身从按下到抬起,而无需知道初始值。

The RenderNode's transform matrix is computed at render time as follows:

某一时刻RenderNode的变换矩阵是按照以下方式进行计算的:

    Matrix transform = new Matrix();
    transform.setTranslate(renderNode.getTranslationX(), renderNode.getTranslationY());
    transform.preRotate(renderNode.getRotationZ(),
            renderNode.getPivotX(), renderNode.getPivotY());
    transform.preScale(renderNode.getScaleX(), renderNode.getScaleY(),
            renderNode.getPivotX(), renderNode.getPivotY());

The current canvas transform matrix, which is translated to the RenderNode's position, is then multiplied by the RenderNode's transform matrix. Therefore the ordering of calling property setters does not affect the result. That is to say that:

当前Canvas的变换矩阵,已经被转换到了RenderNode的位置,然后乘以RenderNode的变换矩阵。因此调用属性设置符的顺序不会影响结果,也就是说:

    renderNode.setTranslationX(100);
    renderNode.setScaleX(100);

is equivalent to:

等同于:

    renderNode.setScaleX(100);
    renderNode.setTranslationX(100);

Threading

RenderNode may be created and used on any thread but they are not thread-safe. Only a single thread may interact with a RenderNode at any given time. It is critical that the RenderNode is only used on the same thread it is drawn with. For example when using RenderNode with a custom View, then that RenderNode must only be used from the UI thread.

RenderNode可以在任何线程上被创建和使用,但是它们并不是线程安全的。在任何给定的时间,只有一个线程可以与RenderNode交互。 至关重要的是,RenderNode 仅在与它一起绘制的同一线程上使用。比如,当在一个自定义View上使用RenderNode时,那个RenderNode必须只能在UI线程上使用。

When to re-render

Many of the RenderNode mutation methods, such as {@link #setTranslationX(float)}, return a boolean indicating if the value actually changed or not. This is useful in detecting if a new frame should be rendered or not. A typical usage would look like:

许多RenderNode的变异方法,如#setTranslationX(float) ,返回一个boolean类型的值来表示值是否真的被改变了。这在检测一个新的帧是否应该被渲染的时候很有用。一个典型的用法可能像这样:

    public void translateTo(int x, int y) {
        boolean needsUpdate = myRenderNode.setTranslationX(x);
        needsUpdate |= myRenderNode.setTranslationY(y);
        if (needsUpdate) {
            myOwningView.invalidate();
        }
    }

This is marginally faster than doing a more explicit up-front check if the value changed by comparing the desired value against {@link #getTranslationX()} as it minimizes JNI transitions. The actual mechanism of requesting a new frame to be rendered will depend on how this RenderNode is being drawn. If it's drawn to a containing View, as in the above snippet, then simply invalidating that View works. If instead the RenderNode is being drawn to a Canvas directly such as with {@link Surface#lockHardwareCanvas()} then a new frame needs to be drawn by calling {@link Surface#lockHardwareCanvas()}, re-drawing the root RenderNode or whatever top-level content is desired, and finally calling {@link Surface#unlockCanvasAndPost(Canvas)}.

一种更加明确的预先检查期望值是否改变的方法是,将期望值与{@link #getTranslationX()} 进行比较,这里的方法比这种方法要快一点,因为它最大限度地减少了JNI转换。请求渲染新帧的实际机制将依赖RenderNode以何种方式绘制。如果它被绘制到了一个包含的View中,如上面的代码片段所示,那么只需简单地将该View无效化即可。如果反之这个RenderNode被直接绘制到了一个Canvas上了,比如通过Surface#lockHardwareCanvas() ,那么一个新帧需要通过调用Surface#lockHardwareCanvas() 来绘制,重绘根RenderNode或者任何所需的顶级内容,最后调用Surface#unlockCanvasAndPost(Canvas)

3.3 RenderNode.beginRecording

RenderNode.beginRecording方法定义为:

    /**
     * Starts recording a display list for the render node. All
     * operations performed on the returned canvas are recorded and
     * stored in this display list.
     *
     * {@link #endRecording()} must be called when the recording is finished in order to apply
     * the updated display list. Failing to call {@link #endRecording()} will result in an
     * {@link IllegalStateException} if {@link #beginRecording(int, int)} is called again.
     *
     * @param width  The width of the recording viewport. This will not alter the width of the
     *               RenderNode itself, that must be set with {@link #setPosition(Rect)}.
     * @param height The height of the recording viewport. This will not alter the height of the
     *               RenderNode itself, that must be set with {@link #setPosition(Rect)}.
     * @return A canvas to record drawing operations.
     * @throws IllegalStateException If a recording is already in progress. That is, the previous
     * call to {@link #beginRecording(int, int)} did not call {@link #endRecording()}.
     * @see #endRecording()
     * @see #hasDisplayList()
     */
	public @NonNull RecordingCanvas beginRecording(int width, int height) {
        if (mCurrentRecordingCanvas != null) {
            throw new IllegalStateException(
                    "Recording currently in progress - missing #endRecording() call?");
        }
        mCurrentRecordingCanvas = RecordingCanvas.obtain(this, width, height);
        return mCurrentRecordingCanvas;
    }

    static RecordingCanvas obtain(@NonNull RenderNode node, int width, int height) {
        if (node == null) throw new IllegalArgumentException("node cannot be null");
        RecordingCanvas canvas = sPool.acquire();
        if (canvas == null) {
            canvas = new RecordingCanvas(node, width, height);
        } else {
            nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, node.mNativeRenderNode,
                    width, height);
        }
        canvas.mNode = node;
        canvas.mWidth = width;
        canvas.mHeight = height;
        return canvas;
    }

1)、RecordingCanvas.beginRecording方法从RecordingCanvas.obtain方法中获取一个RecordingCanvas对象,保存在RecordingCanvas.mCurrentRecordingCanvas中。

2)、RecordingCanvas.obtain方法首先是从一个RecordingCanvas对象池中请求一个RecordingCanvas对象。如果获取失败,再直接创建一个RecordingCanvas象。在将获取到的RecordingCanvas对象返回给调用者之前,还会将参数node描述的Render Node保存在其成员变量mNode中。

3.3.1 RecordingCanvas.init

首先看下RecordingCanvas类的定义:

/**
 * A Canvas implementation that records view system drawing operations for deferred rendering.
 * This is used in combination with RenderNode. This class keeps a list of all the Paint and
 * Bitmap objects that it draws, preventing the backing memory of Bitmaps from being released while
 * the RecordingCanvas is still holding a native reference to the memory.
 *
 * This is obtained by calling {@link RenderNode#beginRecording()} and is valid until the matching
 * {@link RenderNode#endRecording()} is called. It must not be retained beyond that as it is
 * internally reused.
 */
public final class RecordingCanvas extends BaseRecordingCanvas

为延迟渲染记录View系统绘制操作的 Canvas 实现。

RecordingCanvas的构造方法为:

    private RecordingCanvas(@NonNull RenderNode node, int width, int height) {
        super(nCreateDisplayListCanvas(node.mNativeRenderNode, width, height));
        mDensity = 0; // disable bitmap density scaling
    }

    public BaseRecordingCanvas(long nativeCanvas) {
        super(nativeCanvas);
    }

    /**
     *  @hide Needed by android.graphics.pdf.PdfDocument, but should not be called from
     *  outside the UI rendering module.
     */
    public Canvas(long nativeCanvas) {
        if (nativeCanvas == 0) {
            throw new IllegalStateException();
        }
        mNativeCanvasWrapper = nativeCanvas;
        // ......
    }

重点在于这个JNI函数,nCreateDisplayListCanvas,其对应的是android_graphics_DisplayListCanvas中的android_view_DisplayListCanvas_createDisplayListCanvas函数:

// frameworks\base\libs\hwui\jni\android_graphics_DisplayListCanvas.cpp

static JNINativeMethod gMethods[] = {
        // ------------ @CriticalNative --------------
        {"nCreateDisplayListCanvas", "(JII)J",
         (void*)android_view_DisplayListCanvas_createDisplayListCanvas},
        // ......
};

那么Java层的Canvas本质就是对Native层的Canvas的一层封装,调用JNI函数nCreateDisplayListCanvas创建Native层Canvas后,将其地址保存在成员变量mNativeCanvasWrapper中。

3.3.2 android_view_DisplayListCanvas_createDisplayListCanvas

继续看android_view_DisplayListCanvas_createDisplayListCanvas函数:

// frameworks\base\libs\hwui\jni\android_graphics_DisplayListCanvas.cpp

static jlong android_view_DisplayListCanvas_createDisplayListCanvas(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
        jint width, jint height) {
    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
    return reinterpret_cast<jlong>(Canvas::create_recording_canvas(width, height, renderNode));
}

// frameworks\base\libs\hwui\hwui\Canvas.cpp

Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::RenderNode* renderNode) {
    return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
}

这里看到,在native层实际创建的是一个SkiaRecordingCanvas对象,然后返回了该Canvas的地址。

SkiaRecordingCanvas类的定义为:

/**
 * A SkiaCanvas implementation that records drawing operations for deferred rendering backed by a
 * SkLiteRecorder and a SkiaDisplayList.
 */
class SkiaRecordingCanvas : public SkiaCanvas {
public:
    explicit SkiaRecordingCanvas(uirenderer::RenderNode* renderNode, int width, int height) {
        initDisplayList(renderNode, width, height);
    }

    // ......

private:
    RecordingCanvas mRecorder;
    std::unique_ptr<SkiaDisplayList> mDisplayList;

    // ......
};

一个 SkiaCanvas 实现,它记录由 SkLiteRecorder 和 SkiaDisplayList 支持的延迟渲染的绘图操作。

3.3.3 SkiaRecordingCanvas.initDisplayList

接下来看其构造函数中的初始化函数initDisplayList:

void SkiaRecordingCanvas::initDisplayList(uirenderer::RenderNode* renderNode, int width,
                                          int height) {
    mCurrentBarrier = nullptr;
    SkASSERT(mDisplayList.get() == nullptr);

    if (renderNode) {
        mDisplayList = renderNode->detachAvailableList();
    }
    if (!mDisplayList) {
        mDisplayList.reset(new SkiaDisplayList());
    }

    mDisplayList->attachRecorder(&mRecorder, SkIRect::MakeWH(width, height));
    SkiaCanvas::reset(&mRecorder);
    mDisplayList->setHasHolePunches(false);
}

1)、首先调用RenderNode.detachAvailableList函数,查看传参RenderNode中上一帧缓存的SkiaDisplayList是否还在,如果在直接赋值给SkiaRecordingCanvas的SkiaDisplayList类型的成员变量mDisplayList,否则创建一个新的SkiaDisplayList对象。

2)、接着调用SkiaDisplayList.attachRecorder函数:

    void attachRecorder(RecordingCanvas* recorder, const SkIRect& bounds) {
        recorder->reset(&mDisplayList, bounds);
    }

SkiaDisplayList的成员变量mDisplayList为DisplayListData类型。

void RecordingCanvas::reset(DisplayListData* dl, const SkIRect& bounds) {
    this->resetCanvas(bounds.right(), bounds.bottom());
    fDL = dl;
    mClipMayBeComplex = false;
    mSaveCount = mComplexSaveCount = 0;
}

这里是将RecordingCanvas的DisplayListData类型的成员变量fDL指向了SkiaDisplayList的成员变量mDisplayList。

那么这里SkiaRecordingCanvas的两个成员变量,RecordingCanvas类型的mRecorder,以及SkiaDisplayList类型的mDisplayList,便通过内部的DisplayListData建立了联系。

3)、最后这里又调用了SkiaCanvas.reset函数:

void SkiaCanvas::reset(SkCanvas* skiaCanvas) {
    if (mCanvas != skiaCanvas) {
        mCanvas = skiaCanvas;
        mCanvasOwned.reset();
    }
    mSaveStack.reset(nullptr);
}

将SkiaCanvas的SkCanvas类型的成员变量指向了传入的RecordingCanvas对象,因为RecordingCanvas是SkCanvas的子类,RecordingCanvas类的继承关系为:

// frameworks\base\libs\hwui\RecordingCanvas.h
class RecordingCanvas final : public SkCanvasVirtualEnforcer<SkNoDrawCanvas>

// external\skia\include\utils\SkNoDrawCanvas.h    
class SK_API SkNoDrawCanvas : public SkCanvasVirtualEnforcer<SkCanvas>

SkCanvasVirtualEnforcer是一个模板类:

template <typename Base>
class SkCanvasVirtualEnforcer : public Base

因此RecordingCanvas的实际父类是SkNoDrawCanvas和SkCanvas。

3.4 记录绘制命令

回到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)) {
            // ......
            
            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 {
            // ......
        }
        // ......
    }

首先探究一下这里的局部变量layerType,layerType通过View.getLayerType方法得到,View.getLayerType返回了这个View当前相关联的图层类型:

    /**
     * Indicates what type of layer is currently associated with this view. By default
     * a view does not have a layer, and the layer type is {@link #LAYER_TYPE_NONE}.
     * Refer to the documentation of {@link #setLayerType(int, android.graphics.Paint)}
     * for more information on the different types of layers.
     *
     * @return {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
     *         {@link #LAYER_TYPE_HARDWARE}
     *
     * @see #setLayerType(int, android.graphics.Paint)
     * @see #buildLayer()
     * @see #LAYER_TYPE_NONE
     * @see #LAYER_TYPE_SOFTWARE
     * @see #LAYER_TYPE_HARDWARE
     */
    @InspectableProperty(enumMapping = {
            @EnumEntry(value = LAYER_TYPE_NONE, name = "none"),
            @EnumEntry(value = LAYER_TYPE_SOFTWARE, name = "software"),
            @EnumEntry(value = LAYER_TYPE_HARDWARE, name = "hardware")
    })
    @LayerType
    public int getLayerType() {
        return mLayerType;
    }

1)、LAYER_TYPE_NONE,表示View没有一个Layer。

2)、LAYER_TYPE_SOFTWARE,表示View有一个Software Layer。Software Layer由Bimap支持,并且使用Android 的软件渲染管道渲染View,即使启用了硬件加速也是如此。

3)、LAYER_TYPE_HARDWARE,表示View有一个Hareware Layer。Hareware Layer由硬件特定纹理(通常是 OpenGL 硬件上的帧缓冲区对象(Frame Buffer Object)或者说 FBO)支持,并导致使用 Android 的硬件渲染管道渲染视图,但前提是为视图层次结构打开了硬件加速。

这里进一步扩展一下View的Layer:

所谓 View Layer,又称为离屏缓冲(Off-screen Buffer),它的作用是单独启用一块地方来绘制这个 View ,而不是使用软件绘制的 Bitmap 或者通过硬件加速的 GPU。这块「地方」可能是一块单独的 Bitmap,也可能是一块 OpenGL 的纹理(texture,OpenGL 的纹理可以简单理解为图像的意思),具体取决于硬件加速是否开启。采用什么来绘制 View 不是关键,关键在于当设置了 View Layer 的时候,它的绘制会被缓存下来,而且缓存的是最终的绘制结果,而不是像硬件加速那样只是把 GPU 的操作保存下来再交给 GPU 去计算。通过这样更进一步的缓存方式,View 的重绘效率进一步提高了:只要绘制的内容没有变,那么不论是 CPU 绘制还是 GPU 绘制,它们都不用重新计算,而只要只用之前缓存的绘制结果就可以了。

默认情况下View的mLayerType为LAYER_TYPE_NONE,因此大部分情况下RenderNode.updateDisplayListIfDirty方法里走的逻辑为:

                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        dispatchDraw(canvas);
                        // ......
                    } else {
                        draw(canvas);
                    }

这时候当前正在处理的View的成员变量mPrivateFlags的值的PFLAG_SKIP_DRAW位设置为1,就表明当前正在处理的View是一个Layout,并且没有设置Background,这时候就可以走一个捷径,即直接调用View类的成员函数dispatchDraw将该Layout的子View的UI绘制在当前正在处理的View关联的Render Node对应的RecordingCanvas即可。另一方面,如果当前正在处理的View的成员变量mPrivateFlags的值的PFLAG_SKIP_DRAW位设置为0,那么就不能走捷径,而是走一个慢路径,规规矩矩地调用View 类的成员函数draw将当前正在处理的View及其可能存在的子View的UI绘制在关联的Render Node对应的RecordingCanvas上。

View.dispatchDraw相当于直接跳过了当前View的绘制直接去绘制其子View,我们还是看下通用流程下的View.draw。

3.4.1 View.draw

    /**
     * Manually render this view (and all of its children) to the given Canvas.
     * The view must have already done a full layout before this function is
     * called.  When implementing a view, implement
     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
     * If you do need to override this method, call the superclass version.
     *
     * @param canvas The Canvas to which the View is rendered.
     */
    @CallSuper
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         *      7. If necessary, draw the default focus highlight
         */

        // ......
        
        // Step 3, draw the content
        long logTime = System.currentTimeMillis();
        onDraw(canvas);
        
        // ......
    }

一个View的主要UI是由子类实现的成员函数onDraw绘制的。这个成员函数通过参数canvas可以获得一个Canvas,然后就调用这个Canvas提供的API就可以绘制一个View的UI。这意味着对一个View来说,当它的成员函数onDraw被调用时,它是不需要区别它是通过硬件渲染还是软件渲染的。但是从结果来看,当使用硬件渲染时,调用Canvas API相当是将API调用记录在一个Display List中,而当使用软件渲染时,调用Canvas API相当是将UI绘制在一个Bitmap中。

此外,对于使用硬件渲染的View来说,它的Background也是抽象为一个Render Node绘制在宿主View关联的一个Render Node对应的RecordingCanvas上的,相当于是将Background看作是一个View的子View。

这里继续分析View.onDraw。

3.4.2 View.onDraw

    /**
     * Implement this to do your drawing.
     *
     * @param canvas the canvas on which the background will be drawn
     */
    protected void onDraw(Canvas canvas) {
    }

View的onDraw方法需要其子类自行实现,具体就是调用Canvas的各种drawXXX的API,如drawLine、draw Bitmap和drawColor等进行绘制,拿上面的google的例子:

      RenderNode renderNode = new RenderNode("myRenderNode");
      renderNode.setPosition(0, 0, 50, 50); // Set the size to 50x50
      RecordingCanvas canvas = renderNode.beginRecording();
      try {
          // Draw with the canvas
          canvas.drawRect(...);
      } finally {
          renderNode.endRecording();
      }

在创建一个RenderNode,并且通过RenderNode.beginRecording获取一个RecordingCanvas后,下一步就可以拿这个RecordingCanvas进行绘制了,即这里的:

          // Draw with the canvas
          canvas.drawRect(...);

看下Canvas.drawRect的实现。

3.4.3 BaseRecordingCanvas.drawRect

根据之前的分析我们知道,在我们分析的流程中,View.onDraw方法中传入的Canvas是RenderNode.beginRecording方法返回的一个RecordingCanvas对象,但是由于RecordingCanvas没有重写drawRect方法,所以这里调用的是其父类BaseRecordingCanvas的drawRect方法:

// frameworks\base\graphics\java\android\graphics\BaseRecordingCanvas.java

	@Override
    public final void drawRect(float left, float top, float right, float bottom,
            @NonNull Paint paint) {
        nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
    }

对应的JNI函数为:

// frameworks\base\libs\hwui\jni\android_graphics_Canvas.cpp

static void drawRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
                     jfloat right, jfloat bottom, jlong paintHandle) {
    const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
    get_canvas(canvasHandle)->drawRect(left, top, right, bottom, *paint);
}

由于Java层Canvas成员变量mNativeCanvasWrapper保存了之前Native层的创建的一个SkiaRecordingCanvas对象的地址,那么这里的get_canvas获取的就是一个SkiaRecordingCanvas对象,但是SkiaRecordingCanvas并没有一个drawRect函数,所以这里调用的是其父类SkiaCanvas的drawRect函数。

3.4.4 SkiaCanvas.drawRect

// frameworks\base\libs\hwui\SkiaCanvas.cpp

void SkiaCanvas::drawRect(float left, float top, float right, float bottom, const Paint& paint) {
    if (CC_UNLIKELY(paint.nothingToDraw())) return;
    applyLooper(&paint, [&](const SkPaint& p) {
        mCanvas->drawRect({left, top, right, bottom}, p);
    });
}

SkiaCanvas的成员变量mCanvas之前分析过,是一个RecordingCanvas对象,那么这里最终调用的是RecordingCanvas.drawRect。

从SkiaRecordingCanvas.initDisplayList函数中我们了解到,这里的mCanvas也不是一个SkCanvas对象,而是SkCanvas的一个子类,RecordingCanvas。但是RecordingCanvas没有自己的drawRect函数,所以这里调用的依旧是SkCanvas.drawRect。

3.4.5 SkCanvas.drawRect

// frameworks\base\libs\hwui\SkiaCanvas.cpp

void SkCanvas::drawRect(const SkRect& r, const SkPaint& paint) {
    TRACE_EVENT0("skia", TRACE_FUNC);
    // To avoid redundant logic in our culling code and various backends, we always sort rects
    // before passing them along.
    this->onDrawRect(r.makeSorted(), paint);
}

调用onDrawRect函数,这个函数RecordingCanvas复写了,所以调用的是RecordingCanvas.onDrawRect。

3.4.6 RecordingCanvas.onDrawRect

// frameworks\base\libs\hwui\RecordingCanvas.cpp

void RecordingCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) {
    fDL->drawRect(rect, paint);
}

从SkiaRecordingCanvas.initDisplayList中我们知道这里RecordingCanvas的成员函数fDL已经指向了一个DisplayListData对象,所以RecordingCanvas.drawRect调用的是DisplayListData.drawRect。

3.4.7 DisplayListData.draw

// frameworks\base\libs\hwui\RecordingCanvas.cpp

void DisplayListData::drawRect(const SkRect& rect, const SkPaint& paint) {
    this->push<DrawRect>(0, rect, paint);
}

在DisplayListData.drawRect中,则是push了一个DrawRect的OP:

struct DrawRect final : Op {
    static const auto kType = Type::DrawRect;
    DrawRect(const SkRect& rect, const SkPaint& paint) : rect(rect), paint(paint) {}
    SkRect rect;
    SkPaint paint;
    void draw(SkCanvas* c, const SkMatrix&) const { c->drawRect(rect, paint); }
};

struct Op {
    uint32_t type : 8;
	uint32_t skip : 24;
};

OP代表了某一种操作,type代表了OP的类型,skip则代表了该OP占内存的大小。

该类中该定义有很多其他OP,如:

struct DrawPath final : Op 

struct DrawRegion final : Op

接下来看DisplayListData.push。

3.4.8 DisplayListData.push

首先看下DisplayListData的一些成员变量:

class DisplayListData final {
public:
	// ......

    SkAutoTMalloc<uint8_t> fBytes;
    size_t fUsed = 0;
    size_t fReserved = 0;

    bool mHasText : 1;
};

fBytes代表了一块内存空间起始地址,fUsed表明这块内存已经使用了多少空间,fReserved代表的是被分配了多少空间。

template <typename T, typename... Args>
void* DisplayListData::push(size_t pod, Args&&... args) {
    size_t skip = SkAlignPtr(sizeof(T) + pod);
    SkASSERT(skip < (1 << 24));
    if (fUsed + skip > fReserved) {
        static_assert(SkIsPow2(SKLITEDL_PAGE), "This math needs updating for non-pow2.");
        // Next greater multiple of SKLITEDL_PAGE.
        fReserved = (fUsed + skip + SKLITEDL_PAGE) & ~(SKLITEDL_PAGE - 1);
        fBytes.realloc(fReserved);
        LOG_ALWAYS_FATAL_IF(fBytes.get() == nullptr, "realloc(%zd) failed", fReserved);
    }
    SkASSERT(fUsed + skip <= fReserved);
    auto op = (T*)(fBytes.get() + fUsed);
    fUsed += skip;
    new (op) T{std::forward<Args>(args)...};
    op->type = (uint32_t)T::kType;
    op->skip = skip;
    return op + 1;
}

DataListData的push函数,就是计算每一条绘制指令的占用空间,然后将这些绘制指令存储到以fBytes为起始位置的内存空间中。

那么再次回看Java层RecordingCanvas的类定义:

A Canvas implementation that records view system drawing operations for deferred rendering.

这么看的确没错,绘制操作被记录到了一个Native层SkiaRecordingCanvas的SkiaDisplayList类型的成员变量mDisplayList的DisplayListData类型的成员变量mDisplayList中,而该SkiaRecordingCanvas的地址则由一个Java层的RecordingCanvas对象保存。

3.5 RenderNode.endRecording

    /**
     * `
     * Ends the recording for this display list. Calling this method marks
     * the display list valid and {@link #hasDisplayList()} will return true.
     *
     * @see #beginRecording(int, int)
     * @see #hasDisplayList()
     */
    public void endRecording() {
        if (mCurrentRecordingCanvas == null) {
            throw new IllegalStateException(
                    "No recording in progress, forgot to call #beginRecording()?");
        }
        RecordingCanvas canvas = mCurrentRecordingCanvas;
        mCurrentRecordingCanvas = null;
        canvas.finishRecording(this);
        canvas.recycle();
    }

停止记录当前的DisplayList。调用此方法将会标记DisplayList为可用,RenderNode.hasDisplayList方法将会返回true。

3.5.1 RecordingCanvas.finishRecording

    void finishRecording(RenderNode node) {
        nFinishRecording(mNativeCanvasWrapper, node.mNativeRenderNode);
    }

对应的是android_view_DisplayListCanvas_finishRecording函数。

// frameworks\base\libs\hwui\jni\android_graphics_DisplayListCanvas.cpp

static void android_view_DisplayListCanvas_finishRecording(
        CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jlong renderNodePtr) {
    Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
    canvas->finishRecording(renderNode);
}

传入的分别是之前创建Native层SkiaRecordingCanvas对象和RenderNode对象时返回给Java层的对象地址,那么这里可以根据这些地址拿到对应的SkiaRecordingCanvas对象和RenderNode对象。

因此这里继续调用SkiaRecordingCanvas.finishRecording。

3.5.2 SkiaRecordingCanvas.finishRecording

std::unique_ptr<SkiaDisplayList> SkiaRecordingCanvas::finishRecording() {
    // close any existing chunks if necessary
    enableZ(false);
    mRecorder.restoreToCount(1);
    return std::move(mDisplayList);
}

void SkiaRecordingCanvas::finishRecording(uirenderer::RenderNode* destination) {
    destination->setStagingDisplayList(uirenderer::DisplayList(finishRecording()));
}

1)、无参的SkiaRecordingCanvas.finishRecording返回的是一个SkiaDisplayList对象,之前View的各种绘制命令是记录到了这个SkiaDisplayList的DisplayListData类型的成员变量mDisplayList中。

2)、这里将SkiaDisplayList封装到一个DisplayList对象中,然后调用RenderNode.setStagingDisplayList。

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)) {}
    
    // ......
    
private:
    std::unique_ptr<skiapipeline::SkiaDisplayList> mImpl;
};   

// ......

// For now stick to the original single-type container to avoid any regressions
using DisplayList = SkiaDisplayListWrapper;

这里使用了别名“DisplayList”替代了原始类型“SkiaDisplayListWrapper”。

DisplayList,或SkiaDisplayListWrapper,即是对SkiaDisplayList的一层封装,真正的SkiaDisplayList保存在DisplayList的成员变量mImpl中。

3.5.3 RenderNode.setStagingDisplayList

void RenderNode::setStagingDisplayList(DisplayList&& newData) {
    mValid = newData.isValid();
    mNeedsDisplayListSync = true;
    mStagingDisplayList = std::move(newData);
}

1)、RenderNode.mValid定义为:

    // Owned by UI. Set when DL is set, cleared when DL cleared or when node detached
    // (likely by parent re-record/removal)
    bool mValid = false;

表明一个RenderNode是否设置一了个DisplayList。

根据的就是这里传入的DisplayList中封装的SkiaDisplayList是否为空。

    // If true this DisplayList contains a backing content, even if that content is empty
    // If false, there this DisplayList is in an "empty" state
    [[nodiscard]] bool isValid() const {
        return mImpl.get() != nullptr;
    }

2)、接着将RenderNode的DisplayList类型的成员变量mStagingDisplayList指向参数newData:

    DisplayList mStagingDisplayList;

绘制命令是保存在SkiaDisplayList的DisplayListData类型的成员变量mDisplayList中,而DisplayList封装了SkiaDisplayList,那么RenderNode保存了DisplayList,实际上就是保存了绘制该RenderNode对应的View所需的绘制命令。

3.6 小结

class.png

1)、RenderNode.beginRecording,在Native创建了一个SkiaRecordingCanvas,并且初始化了其类型为SkiaDisplayList和RecordingCanvas的成员变量。

2)、经过View.onDraw,绘制当前View的绘制命令保存在了Native层SkiaRecordingCanvas对象的,SkiaDisplayList类型的成员变量mDisplayList的,DisplayListData类型的成员变量中。

3)、经过RecordingCanvas.endRecording,SkiaDisplayList的所有权发生了转移,从SkiaRecordingCanvas到了RenderNode中。由于SkiaDisplayList中保存着存储了绘制命令的DisplayListData,那么SkiaDisplayList从SkiaRecordingCanvas转移到RenderNode,实际上也就是绘制命令的存储点从SkiaRecordingCanvas转移到了RenderNode。

至于为何要有这样的转移,回看RenderNode.beginRecording的内容,会发现每次想通过RenderNode.beginRecording方法获取一个RecordingCanvas对象时,是优先从一个RecordingCanvas对象池中去取的,那么这里就存在一个RecordingCanvas对象的回收利用。虽然DisplayList的信息最开始是保存在SkiaRecordingCanvas的SkiaDisplayList中的,但是SkiaRecordingCanvas并不和一个View或者RenderNode是一一对应的关系,因此如果想要根据View树结构构建一个DisplayList树,依靠SkiaRecordingCanvas是不可能的,因此才需要将DisplayList的信息从SkiaRecordingCanvas转移到RenderNode中。

4 构建DisplayList树

经过ThreadedRenderer.updateViewTreeDisplayList方法,我们成功将绘制每一个View的命令保存在了这个View对应的RenderNode中的DisplayList中了,但是似乎这棵DisplayList树还没有建立起来,原因则在于每个RenderNode还是独立的,彼此并没有建立起联系。

当绘制流程走到某一个View时,有两件事是这个View需要做的,一是根据需要绘制自身,二是发起对子View的绘制调用命令。

要知道DisplayList树是如何构建的,就需要探究一下View层级结构中的每个View的绘制方法的调用是如何从上到下进行分发的。

4.1 Draw方法的调用流程

这里拿一个我自己创建的一个View层级结构很简单的Activity为例:

example.png

不看StatusBar和NavigationBar的背景,该Activity从顶层的DecorView到底层的TextView只有5层,每层一个View:

DecorView
	LinearLayout
		FrameLayout
			ConstraintLayout
				TextView

4.1.1 View.updateDisplayListIfDirty

根据之前的分析,在经过以下流程后:

ViewRootImpl.draw
	-> ThreadedRenderer.draw
		-> ThreadedRenderer.updateRootDisplayList
			-> ThreadedRenderer.updateViewTreeDisplayList
				-> View.updateDisplayListIfDirty

首先是一个View层级结构中的顶层View走到了View.updateDisplayListIfDirty,对Activity来说就是DecorView。

只聚焦View的绘制方法的调用流程:

                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        dispatchDraw(canvas);
                        // ......
                    } else {
                        draw(canvas);
                    }

之前讲过,PFLAG_SKIP_DRAW这个标志位表示是否跳过当前View的绘制,比如例子中的Activity的View层级结构中的LinearLayout、FrameLayout等,最主要的用处是作为其他View的容器,它本身是没有内容需要绘制的,因此这些Layout默认是无需绘制的,因此在这里走的是ViewGroup.dispatchDraw。而像TextView等有内容的View,这里走的则是View.draw。

这里继续看View.draw方法,无需看ViewGroup.dispatchDraw,因为View.draw方法中也有对于ViewGroup.dispatchDraw的调用。

4.1.2 View.draw(Canvas)

    public void draw(Canvas canvas) {
        // ......
        
        // Step 3, draw the content
        long logTime = System.currentTimeMillis();
        onDraw(canvas);
        
        // ......
        
        // Step 4, draw the children
        dispatchDraw(canvas);
        
        // ......
    }

只关注以下两部分:

1)、调用View.onDraw,绘制当前View。

2)、调用View.dispatchDraw,发起对子View的绘制命令的调用。

4.1.3 View.dispatchDraw

    /**
     * Called by draw to draw the child views. This may be overridden
     * by derived classes to gain control just before its children are drawn
     * (but after its own view has been drawn).
     * @param canvas the canvas on which to draw the view
     */
    protected void dispatchDraw(Canvas canvas) {

    }

由 draw 调用来绘制子View。 这可能会被派生类覆盖,以便在绘制其子View之前(但在绘制自己的View之后)获得控制权。

如果当前View没有子View,那么什么也不用做,否之则需要调用自己的dispatchDraw方法来发起对子View的draw方法的调用。

在我们所举的例子中,LInearLayout等Layout调用的都是ViewGroup.dispatchDraw。

4.1.4 ViewGroup.dispatchDraw

    @Override
    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);
                }
                // ......
            }

            // ......
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        while (transientIndex >= 0) {
            // ......
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                    transientChild.getAnimation() != null) {
                more |= drawChild(canvas, transientChild, drawingTime);
            }
            // ......
        }
        
        // ......
        
        // Draw any disappearing views that have animations
        if (mDisappearingChildren != null) {
            // ......
            for (int i = disappearingCount; i >= 0; i--) {
                final View child = disappearingChildren.get(i);
                more |= drawChild(canvas, child, drawingTime);
            }
        }
    }

继续调用了ViewGroup.drawChild。

4.1.5 ViewGroup.drawChild

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

调用了View的另外一个同名方法draw(Canvas, ViewGrou, long)。

4.1.6 View.draw(Canvas, ViewGroup, long)

    /**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
     *
     * This is where the View specializes rendering behavior based on layer type,
     * and hardware acceleration.
     */
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();

        boolean drawingWithRenderNode = drawsWithRenderNode(canvas);

        // ......

        if (drawingWithRenderNode) {
            // Delay getting the display list until animation-driven alpha values are
            // set up and possibly passed on to the view
            renderNode = updateDisplayListIfDirty();
            // ......
        }
        
        // ......
        
        final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;

        // ......

        if (!drawingWithDrawingCache) {
            if (drawingWithRenderNode) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((RecordingCanvas) canvas).drawRenderNode(renderNode);
            } else {
                // ......
            }
        } else if (cache != null) {
            // ......
        }

        // ......
    }

1)、局部变量drawingWithRenderNode表明当前View是否绘制在一个支持硬件加速渲染的Canvas上,根据之前的分析知道这里的Canvas为RecordingCanvas,所以这里drawingWithRenderNode为true。

2)、局部变量drawingWithRenderNode为true,那么就会为当前View调用View.updateDisplayListIfDirty,后续的流程又回到了4.1节,实现了View层级结构的迭代,最终每一个View的draw方法都能调用到。

3)、当前View绘制完成后,调用RecordingCanvas.drawRenderNode方法。

4.1.7 小结

以我们这里的Activity为例,首先是DecorView,从View.updateDisplayListIfDirty为起点:

View.updateDisplayListIfDirty	----	(DecorView)
	-> View.draw(Canvas) 	----	(DecorView)
		->ViewGroup.dispatchDraw 	----	(DecorView)
			->ViewGroup.drawChild	----	(DecorView)
				-> View.draw(Canvas, ViewGroup, long)	----	(LinearLayout)
					-> View.updateDisplayListIfDirty 	----	(LinearLayout)

从ViewGroup.drawChild开始,流程从DecorView这一级进入到下一层级LinearLayout。

接着是LinearLayout的部分:

View.updateDisplayListIfDirty  	----	(LinearLayout)
	->ViewGroup.dispatchDraw  	----	(LinearLayout)
		->ViewGroup.drawChild 	----	(LinearLayout)
			-> View.draw(Canvas, ViewGroup, long) 	----	(FrameLayout)
				-> View.updateDisplayListIfDirty 	----	(FrameLayout)

也是从ViewGroup.drawChild开始,流程从LinearLayout这一级进入到下一层级FrameLayout。LinearLayout和DecorView流程的唯一区别是,LinearLayout不需要绘制自身,所以没有View.draw(Canvas)这一步。

因此可以看到,实现迭代的关键一步在于View.draw(Canvas, ViewGroup, long)方法中进行View.updateDisplayListIfDirty的调用。

流程可以总结为:

example_call.png

通用流程:

流程图.png

这里简化了从ThreadedRenderer.draw方法到DecorView的View.draw(Canvas)方法的流程。

4.2 绘制RenderNode

根据上一节的分析,看到了在View层级结构中,除了顶层View没有走View.draw(Canvas, ViewGroup, long)之外,其它View都走了该方法:

    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
		// ......

        if (drawingWithRenderNode) {
            // Delay getting the display list until animation-driven alpha values are
            // set up and possibly passed on to the view
            renderNode = updateDisplayListIfDirty();
            // ......
        }
        
        // ......

        if (!drawingWithDrawingCache) {
            if (drawingWithRenderNode) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((RecordingCanvas) canvas).drawRenderNode(renderNode);
            } else {
                // ......
            }
        } else if (cache != null) {
            // ......
        }

        // ......
    }

再次回顾一下View.draw(Canvas, ViewGroup, long)方法的主要内容。

1)、调用View.updateDisplayListIfDirty,在View.updateDisplayListIfDirty方法中,首先当前View调用RenderNode.beginRecording拿到一个RecordingCanvas,然后在View.onDraw方法中,将绘制当前View的绘制命令记录到这个RecordingCanvas中,然后再通过一系列方法调用,最终将这个RecordingCanvas传给了当前View的子View的View.draw(Canvas, ViewGroup, long)方法中。

2)、同理得知,在当前View的View.draw(Canvas, ViewGroup, long)方法传入的Canvas对象,便是当前View的父View通过RenderNode.beginRecording拿到一个RecordingCanvas。

根据以上情况可以知道两点信息:

  • 当前View的绘制命令,保存在了当前View通过RenderNode.beginRecording申请的一个RecordingCanvas。
  • 这里调用的RecordingCanvas.drawRenderNode,RenderNode是对应的当前View,RecordingCanvas却是父View通过RenderNode.beginRecording申请的一个RecordingCanvas,而非当前View申请的。

知道了以上情况后,就可以继续看RecordingCanvas.drawRenderNode的内容了。

4.2.1 RecordingCanvas.drawRenderNode

    /**
     * Draws the specified display list onto this canvas.
     *
     * @param renderNode The RenderNode to draw.
     */
    @Override
    public void drawRenderNode(@NonNull RenderNode renderNode) {
        nDrawRenderNode(mNativeCanvasWrapper, renderNode.mNativeRenderNode);
    }

nCreateDisplayListCanvas是一个JNI函数,对应的是android_graphics_DisplayListCanvas中的android_view_DisplayListCanvas_createDisplayListCanvas函数。

4.2.2 android_view_DisplayListCanvas_drawRenderNode

// frameworks\base\libs\hwui\jni\android_graphics_DisplayListCanvas.cpp

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就是通过RederNode.beginRecording获取的SkiaRecordingCanvas,继续调用SkiaRecordingCanvas.drawRenderNode。

4.2.3 SkiaRecordingCanvas.drawRenderNode

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);
    auto& renderNodeDrawable = mDisplayList->mChildNodes.back();
    
    // ......
    
    drawDrawable(&renderNodeDrawable);

    // ......
}

1)、SkiaRecordingCanvas的成员变量mDisplayList是一个SkiaDisplayList类型的指针,SkiaDisplayList的成员变量mChildNodes则是一个存储RenderNodeDrawable类型对象的双端队列:

    /**
     * We use std::deque here because (1) we need to iterate through these
     * elements and (2) mDisplayList holds pointers to the elements, so they
     * cannot relocate.
     */
    std::deque<RenderNodeDrawable> mChildNodes;

那么这里是将入参renderNode封装到了一个RenderNodeDrawable对象中,然后插入到了SkiaRecordingCanvas的SkiaDisplayList类型的成员变量mDisplayList的成员变量mChildNodes的队尾。

根据对RenderNode.endRecording的分析,我们知道后续SkiaRecordingCanvas的SkiaDisplayList类型的成员变量mDisplayList的所有权会交给RenderNode,那么可以说,每一个RenderNode通过自己所持有的那个SkiaDisplayList对象的mChildNodes,保存了这个RenderNode的所有子RenderNode。

这么一看,SkiaDisplayList的mChildNodes的作用,和ViewGroup.mChildren类似,串联起了整个DisplayList/View树。

2)、RenderNodeDrawable定义为:

/**
 * This drawable wraps a RenderNode and enables it to be recorded into a list
 * of Skia drawing commands.
 */
class RenderNodeDrawable : public SkDrawable {
public:
    /**
     * Creates a new RenderNodeDrawable backed by a render node.
     *
     * @param node that has to be drawn
     * @param canvas is a recording canvas used to extract its matrix
     * @param composeLayer if the node's layer type is RenderLayer this flag determines whether
     *      we should draw into the contents of the layer or compose the existing contents of the
     *      layer into the canvas.
     */
    explicit RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer = true,
                                bool inReorderingSection = false);
    
    // ......
}

RenderNodeDrawable封装了一个 RenderNode 并使它能够被记录到 Skia 绘图命令列表中。

3)、拿到刚刚封装的RenderNodeDrawable对象,调用drawDrawable函数:

    drawDrawable(&renderNodeDrawable);

此流程和之前分析的drawRect的流程类似,最终是将drawDrawable作为绘制指令存入到了DisplayListData中,唯一有点区别的是,这里的DisplayListData对应的是当前RenderNode的父RenderNode,保存的是绘制父RenderNode所需的所有指令,而这里想保存的是绘制当前RenderNode对应的RenderNodeDrawable的指令。换句话说,每一个DisplayListData里保存了绘制其对应的RenderNode的所有指令,以及该RenderNode的子RenderNode的一条绘制RenderNodeDrawable的指令。

至于RenderNodeDrawable的作用是什么,现在还不得而知,挖个坑。

4.2.4 补充 —— 顶层RenderNode

关于RenderNode还漏了一点内容,上面的RecordingCanvas.drawRenderNode方法调用的发起都是在View.draw(Canvas, ViewGroup, long)中,而对于顶层VIew来说,它是没有走View.draw(Canvas, ViewGroup, long)的,那么它的RenderNode也不是在那里绘制的,而是在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);
    }

我们前面那么说的那么多,分析都是ThreadedRenderer.updateViewTreeDisplayList的内容,该方法根据View树,从上到下构建了为RenderNode为节点的DisplayList树,唯一漏了RootRenderNode,这里的ThreadedRenderer.updateRootDisplayList方法的剩余部分正好补充了RootRenderNode的部分。

4.2.5 小结

其实这一节内容很少,没啥好总结的,只是分析了这么多后,发现SkiaDisplayList在很多地方都起到了非常关键的作用:

  • 存储绘制命令:每个View在绘制时,相关绘制是保存到了SkiaDisplayList的DisplayListData类型的成员变量中,这时SkiaDisplayList由SkiaRecordingCanvas所有。结束绘制后,该SkiaDisplayList的所有权转移到了该View对应的RenderNode,也就是说绘制命令最终保存在了RenderNode中。

  • 构建DisplayList树:对每一个View的绘制调用请求在View树中从上到下进行分发时,会相应的在每个View绘制结束后,调用RecordingCanvas.drawRenderNode,从而将每一个View对应的RenderNode都添加到父RenderNode中,这一步是通过将RenderNode封装为RenderNodeDrawable然后保存到SkiaDisplayList的mChildNodes队列中实现的。

DisplayList树中的每一个RenderNode节点,背后都是SkiaDisplayList在支持。

DisplayList树.png