View 的软件绘制与硬件加速绘制

Posted by JasonWu on December 3, 2020

View 的绘制

承接上文 View 的测量与布局,在整个 View 树完成测量和布局的流程之后,接着执行的是最后一步:绘制渲染 draw 过程。整体流程是: measure -> layout -> draw

发起点同样是 ViewRootImpl,整体的时序图示意如下:

由于 View 的渲染绘制过程涉及到硬件层面,整体的流程更为之复杂。

硬件加速绘制和软件绘制

View 的绘制共分为两种:硬件加速绘制软件绘制。两种绘制方式在 Android 系统中有以下的差别:

  • 硬件加速绘制是借助能高效处理图形计算的 GPU 来完成,而软件绘制则是仅依赖 CPU 来完成。
  • 硬件加速绘制在绘制区域的构建中、渲染过程中做了大量的优化

从 Android 4.0 开始,系统默认开启硬件加速。显然是因为一般情况下,View 通过硬件加速绘制能有更好的图形性能和体验。可通过以下代码判断 View 是否已开启硬件加速:

View.isHardwareAccelerated()

两种绘制的分歧点

承接上文,View 绘制的发起点是在 ViewRootImpl 的 performDraw 中,其后会判断 View 的硬件加速是否已启用。若启用,则使用硬件加速绘制,否则则使用软件绘制,整体时序图如下:

frameworks/base/core/java/android/view
    - ViewRootImpl.java
    - View.java
    - ThreadedRenderer.java

细看下 ViewRootImpl 中 draw 方法中硬件加速绘制与软件绘制的分歧点实现:

private boolean draw(boolean fullRedrawNeeded) {
    ...
    // 判断是否开启了硬件加速
    if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
        ...
        // 使用硬件加速绘制
        mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
    } else {
        ...
        // 否则通过软件绘制
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
            scalingRequired, dirty, surfaceInsets)) {
            return false;
        }
    }
}

接下来会着重分析硬件加速绘制的一系列过程。

硬件加速绘制

Android 系统对于硬件加速绘制流程,有一点很重要的优化是,引入了显示列表DisplayList)的概念。

显示列表,指每个

承接上文,View 的硬件加速绘制是进一步通过 ThreadedRenderer 来完成。其会逐步调用到 View 的

View 树的 DrawOp 集的构建

RenderNode用于构建硬件加速的渲染层次结构。 每个RenderNode都包含一个显示列表以及一组影响显示列表呈现的属性。 默认情况下,RenderNodes在内部用于所有View,通常不直接使用。 RenderNode用于将复杂场景的渲染内容分成较小的片段,然后可以更便宜地对其进行单独更新。 更新场景的一部分仅需要更新少量RenderNode的显示列表或属性,而无需从头开始重新绘制所有内容。 仅在更改其内容时,RenderNode仅需要重新记录其显示列表。 无需通过转换属性重新记录显示列表,也可以转换RenderNodes

RenderThread 渲染 UI 到 Graphic Buffer

Title: I’m title ChildView->ViewGroup: invalidate() ViewGroup->ViewGroup: invalidateChild(dirty) ViewGroup->…: invalidateChildInParent(dirty) …->…: … …->ViewRootImpl: invalidateChildInParent(dirty) ViewRootImpl->ViewRootImpl: invalidateChildInParent(dirty) ViewRootImpl->ViewRootImpl: invalidateRectOnScreen(dirty)\nmDirty.union(dirty) ViewRootImpl->ViewRootImpl: scheduleTraversals() ViewRootImpl->ViewRootImpl: performTraversals() ViewRootImpl->ViewRootImpl: perfromDraw() ViewRootImpl->ViewRootImpl: draw() ViewRootImpl->ViewRootImpl: drawSoftware(surface, dirty)

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
    // Draw with software renderer.
    final Canvas canvas;
    ...
    canvas = mSurface.lockCanvas(dirty);
    ...
    try {
        ...
        mView.draw(canvas);
    } finally {
        ...
        surface.unlockCanvasAndPost(canvas);
    }
}

Title: Software canvas CompatibleCanvas->CompatibleCanvas: () CompatibleCanvas->JNI: nInitRaster() JNI->Canvas\n(Native): initRaster() Canvas\n(Native)->Canvas\n(Native): create_canvas() Canvas\n(Native)->SkiaCanvas\n(Native): new SkiaCanvas\n(Native)->SkCanvas\n(Native): new

ViewRootImpl->Surface: drawSoftware() Surface->Surface: lockCanvas() Surface->Surface\n(native): nativeLockCanvas() Surface\n(native)->Surface\n(native): lock(&buffer)