深入理解 Android - 硬件加速(转载)

1,091 阅读2分钟

转载地址

Android硬件加速原理与实现简介

Android应用程序UI硬件加速渲染的Display List构建过程分析

硬件加速绘制

相对于软件绘制,硬件加速绘制可充分利用 GPU 的性能,极大提高了绘制效率。Android Level 14 及以上默认开启硬件加速,硬件加速的控制和详细讲解参考文档:硬件加速

软件绘制和赢家加速绘制的区别

Android硬件加速原理与实现简介

渲染场景纯软件绘制硬件加速加速效果分析
页面初始化绘制所有 View创建所有 DisplayListGPU 分担了复杂计算任务
在一个复杂页面调用背景透明 TextView.setText(),且调用后其尺寸位置不变重绘脏区所有 View重建 TextViewDisplayList重叠的兄弟节点不需 CPU 重绘, GPU 会自行处理
TextView 逐帧播放 Alpha / Translation / Scale 动画每帧都要重绘脏区所有 View除第一帧同场景2,之后每帧只更新 TextView 对应 RenderNode 的属性刷新一帧性能极大提高,动画流畅度提高
修改 TextView 透明度重绘脏区所有 View直接调用 RenderNode.setAlpha() 更新加速前需全页面遍历,并重绘很多 View;加速后只触发 View.updateDisplayListIfDirty(),不再往下遍历,CPU 执行时间可忽略不计

Display List

Display List 本质是一个缓冲区,它里面记录了即将要执行的绘制命令序列。这些绘制命令最终会转化为 Open GL 命令由 GPU 执行。与直接执行绘制命令相比,先将绘制命令记录在 Display List 中然后再执行有两个好处。

  • 第一:在绘制窗口的下一帧时,若某一个视图的 UI 没有发生变化,那么就不必执行与它相关的 Canvas API,即不用执行它的成员函数 onDraw(),而是直接复用上次构建的 Display List 即可。
  • 第二:在绘制窗口下一帧时,若某一个视图的 UI 发生了变化,但只是一些简单的属性发生了变化,例如位置和透明度等简单属性,直接修改 Display List 的相关属性即可,而不用重新构建,这样也可以不用执行它的成员函数 onDraw()

RenderNode

一个 RenderNode 对应一个 View,包含自身及 子View 的所有 Display List

硬件加速渲染流程

在硬件加速渲染环境中,Android 应用程序窗口的 UI 渲染是分两步进行的。

  • 第一步:构建 Display List,发生在 Main Thread 中。
  • 第二步:渲染 Display List 发生在 Render Thread 中。

构建 Display List

硬件渲染的执行过程,主要在 ThreadRenderer.draw() 方法中实现。

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

    // 2.
    if (attachInfo.mPendingAnimatingRenderNodes != null) {
        final int count = attachInfo.mPendingAnimatingRenderNodes.size();
        for (int i = 0; i < count; i++) {
            registerAnimatingRenderNode(
                    attachInfo.mPendingAnimatingRenderNodes.get(i));
        }
        attachInfo.mPendingAnimatingRenderNodes.clear();
        // We don't need this anymore as subsequent calls to
        // ViewRootImpl#attachRenderNodeAnimator will go directly to us.
        attachInfo.mPendingAnimatingRenderNodes = null;
    }

    // 3.
    int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);

    // 4.
    if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
        setEnabled(false);
        attachInfo.mViewRootImpl.mSurface.release();
        // Invalidate since we failed to draw. This should fetch a Surface
        // if it is still needed or do nothing if we are no longer drawing
        attachInfo.mViewRootImpl.invalidate();
    }
    if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
        attachInfo.mViewRootImpl.invalidate();
    }
}

ThreadRenderer.draw() 方法主要做了如下四件事:

    1. 调用 updateRootDisplayList() 方法构建参数 ViewDisplay List。该 View 为 根视图 Decor View
    1. 调用 registerAnimatingRenderNode() 方法注册动画 Render Node
    1. 调用 nSyncAndDrawFrame() 方法通知 Render Thread 渲染下一帧。
    1. 如果 nSyncAndDrawFrame() 的返回值包含 SYNC_LOST_SURFACE_REWARD_IF_FOUNDSYNC_REDRAW_REQUESTED 标识时,表明 Render Thread 可能需要与 Main Thread 进行信息同步。

先来看 updateRootDisplayList() 的实现。

private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    
    updateViewTreeDisplayList(view);

    if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
        RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
        try {
            final int saveCount = canvas.save();
            canvas.translate(mInsetLeft, mInsetTop);
            callbacks.onPreDraw(canvas);

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

            callbacks.onPostDraw(canvas);
            canvas.restoreToCount(saveCount);
            mRootNodeNeedsUpdate = false;
        } finally {
            mRootNode.endRecording();
        }
    }
    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

updateRootDisplayList() 方法中通过调用 updateViewTreeDisplayList() 方法构建视图树上所有视图的 Display List

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;
}

updateViewTreeDisplayList() 方法中除了设置 mPrivateFlags、mRecreateDisplayList 标识,只调用了 View.updateDisplayListIfDirty() 方法。

public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;


    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.hasDisplayList()
            || (mRecreateDisplayList)) {
        // 不需要重新构建当前 View 的 Display List, 只需要更新或重建 子View 的 Display List
        if (renderNode.hasDisplayList()
                && !mRecreateDisplayList) {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchGetDisplayList();

            return renderNode; // no work needed
        }

        // 代码运行到这里说明要重建当前 View 及其 子View 的 Display List
        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) {
                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 (debugDraw()) {
                        debugDrawFocus(canvas);
                    }
                } else {
                    draw(canvas);
                }
            }
        } finally {
            renderNode.endRecording();
            setDisplayListProperties(renderNode);
        }
    } else {
        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
}

若当前 View 自身不需要重建或更新 Display List 则调用 dispatchGetDisplayList() 方法,分发重建 子View Display ListdispatchGetDisplayList() 方法实现于 ViewGroup

protected void dispatchGetDisplayList() {
    final int count = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < count; i++) {
        final View child = children[i];
        if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
            recreateChildDisplayList(child);
        }
    }
    final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size();
    for (int i = 0; i < transientCount; ++i) {
        View child = mTransientViews.get(i);
        if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
            recreateChildDisplayList(child);
        }
    }
    if (mOverlay != null) {
        View overlayView = mOverlay.getOverlayView();
        recreateChildDisplayList(overlayView);
    }
    if (mDisappearingChildren != null) {
        final ArrayList<View> disappearingChildren = mDisappearingChildren;
        final int disappearingCount = disappearingChildren.size();
        for (int i = 0; i < disappearingCount; ++i) {
            final View child = disappearingChildren.get(i);
            recreateChildDisplayList(child);
        }
    }
}

所有正在显示或正在执行动画的 子View 调用 recreateChildDisplayList() 方法。

private void recreateChildDisplayList(View child) {
    child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
    child.mPrivateFlags &= ~PFLAG_INVALIDATED;
    child.updateDisplayListIfDirty();
    child.mRecreateDisplayList = false;
}

这里 View.updateDisplayListIfDirty() 方法再次被调用,可以发现这是一个递归调用。递归过程如下:

Image

若在递归过程中发现需要重建或更新 Display ListView 则调用 View.draw(Canvas) 方法,并通过 RenderNode.beginRecording()\endRecording() 方法重建或更新其 Display List

总结 Android UI 绘制流程如下(虚线代表递归):

Image

上图来源于:Android硬件加速原理与实现简介