硬件渲染一_绘制阶段上层基本流程

2018-04-03  本文已影响324人  gczxbb

视图经过测量和布局,其大小和位置已经确定,接下来是绘制,将视图显示在固定的屏幕位置。本文基于硬件渲染进行分析,关于软件绘制暂且不讨论。
每个App进程均包含不同的视图UI,系统并不知道您到底需要显示什么样的界面,因此,绘制过程由App进程完成。系统框架向您提供了一块画布和一系列API,用户就可以在自己的进程绘制各类视图(View)。大家都知道,在自定义视图时重写View#onDraw方法,其入参就是一块画布,这块画布是如何产生的呢,它在硬件渲染中到底扮演什么角色,利用画布API是如何实现硬件渲染的呢,有好多疑问,下面慢慢分析。

当App收到刷新命令,在Choreographer控制下,实现一次绘制刷新。
因此,UI绘制入口是ViewRootImpl#performTraversals方法。

private void performTraversals(){
    if (!cancelDraw && !newSurface) {//前面已经过测量与布局,且存在Surface。
        performDraw();
    } else {         
    }
}

//ViewRootImpl#performDraw方法。
private void performDraw() {
    final boolean fullRedrawNeeded = mFullRedrawNeeded;
    try {
        draw(fullRedrawNeeded);
    } finally {
    }
}

什么情况下开启硬件加速渲染?
在draw方法中,若AttachInfo内部HardwareRenderer存在,则硬件渲染,否则软件渲染。
什么时候初始化HardwareRenderer呢?

硬件加入渲染开启

ViewRootImpl#setView代码段。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    //view是DecorView视图,它实现RootViewSurfaceTaker
    if (view instanceof RootViewSurfaceTaker) {
        mSurfaceHolderCallback =((RootViewSurfaceTaker)view).willYouTakeTheSurface();
        if (mSurfaceHolderCallback != null) {
            mSurfaceHolder = new TakenSurfaceHolder();
            mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
        }
    }
    if (mSurfaceHolder == null) {
        enableHardwareAcceleration(attrs);//开启硬件加速
    }
    ...
}

private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
    //应用在兼容模式时,不开启 
    //永久进程包括系统进程在低端设备中不加速
    ...
    //创建ThreadedRenderer对象
    mAttachInfo.mHardwareRenderer = HardwareRenderer.create(mContext, translucent);
    ...
}

创建一个ThreadedRenderer,负责硬件渲染,保存在AttachInfo内部,它继承HardwareRenderer。
ViewRootImpl#draw方法代码段。

if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {  
    mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);//硬件渲染入口。
}

总结
在Java层,硬件渲染由ThreadedRenderer负责,每个窗体ViewRootImpl有一个ThreadedRenderer。ThreadedRenderer#draw方法是硬件渲染绘制的入口。


ThreadedRenderer分析

ThreadedRenderer#构造方法。

ThreadedRenderer(Context context, boolean translucent) {
    ....
    long rootNodePtr = nCreateRootRenderNode();
    mRootNode = RenderNode.adopt(rootNodePtr);
    mRootNode.setClipToBounds(false);
    mNativeProxy = nCreateProxy(translucent, rootNodePtr);
    ...
}
ThreadedRenderer与底层结构的关系图。 ThreadedRenderer与底层结构的关系图.jpg

硬件渲染入口方法。

@Override
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
    ...
    //入参view是顶层视图DecorView。
    updateRootDisplayList(view, callbacks);//更新视图树每一个节点。
    final long[] frameInfo = choreographer.mFrameInfo.mFrameInfo;
    //同步绘制帧。
    int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
    ...
}

如何触发树形结构中每一个视图Canvas的相关方法呢?
在自定义View时,重写onDraw方法,将视图绘制成我们想要的样子,其本质就是利用Canvas一系列的drawXxx方法。从updateRootDisplayList开始分析。

updateRootDisplayList分析

ThreadedRenderer#updateRootDisplayList方法。

private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
    //第一步,从顶层视图开始,更新所有视图的DisplayList。
    updateViewTreeDisplayList(view);
    //第二步,根节点绘制顶层视图RenderNode。
    if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
        DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
        try {
            final int saveCount = canvas.save();
            canvas.translate(mInsetLeft, mInsetTop);
            callbacks.onHardwarePreDraw(canvas);
            //插入栅栏,隔离canvas操作
            canvas.insertReorderBarrier();
            //绘制顶层视图RenderNode。
            canvas.drawRenderNode(view.updateDisplayListIfDirty());
            //插入栅栏,隔离canvas操作
            canvas.insertInorderBarrier();
            //回调,ViewRootImpl中实现
            callbacks.onHardwarePostDraw(canvas);
            canvas.restoreToCount(saveCount);
            mRootNodeNeedsUpdate = false;
        } finally {
            mRootNode.end(canvas);
        }
    }
}

主要流程图。

绘制树形视图流程图.jpg 从图片与源码可以看出,两步

ThreadedRenderer#updateViewTreeDisplayList方法。

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();//即DecorView#updateDisplayListIfDirty方法。
    view.mRecreateDisplayList = false;//还原标志。
}

视图私有标志包含PFLAG_INVALIDATED,说明需要绘制,设置mRecreateDisplayList标志,根据该标志重建Canvas当一个视图需要绘制时,上层肯定已经设置PFLAG_INVALIDATED标志。

ThreadedRenderer根RenderNode绘制简要分析

在updateRootDisplayList方法第二步,ThreadedRenderer的根RenderNode创建DisplayListCanvas,DisplayListCanvas#drawRenderNode方法负责绘制DecorView视图的RenderNode。
这里关注的点是,Canvas不属于某一个View,也不是顶层DecorView,我们接下来就会知道,Canvas是View中RenderNode搞出来的,这里的Canvas是最顶层RenderNode生成,RenderNode不依赖某个View,而是专门绘制DecorView的RenderNode。
看一下DisplayListCanvas#drawRenderNode方法,其中的updateDisplayListIfDirty不会再进行一次View树绘制,这时的view依然是DecorView,它的DisplayListCanvas已经end结束记录,View#RenderNode节点mValid已有效,且标志mRecreateDisplayList已被还原。

从顶层视图DecorView,真正开始树形结构递归绘制。
updateDisplayListIfDirty流程图如下。

遍历递归绘制树形视图的处理流程.jpg 每一个视图的流程是一样的,三个步骤,利用视图内部RenderNode创建DisplayListCanvas ,通过View#draw(canvas)方法实现具体记录绘制操作(绘制自身与派发)。在draw方法中包括很多步骤,后面会介绍。RenderNode结束记录。
因此,从DecorView#updateDisplayListIfDirty开始,就已经开始准备遍历递归每一个子视图updateDisplayListIfDirty方法了,进入每一个视图的draw方法。最终,触发每一个视图onDraw。

View#updateDisplayListIfDirty方法。

public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
    // ThreadedRenderer是空,直接返回节点
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()//false,还未记录绘制
            || (mRecreateDisplayList)) {//重建Canvas
        if (renderNode.isValid()
                && !mRecreateDisplayList) {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchGetDisplayList();
            return renderNode; 
        }
        mRecreateDisplayList = true;//重建Canvas
        int width = mRight - mLeft;
        int height = mBottom - mTop;
        int layerType = getLayerType();
        //创建DisplayListCanvas
        final DisplayListCanvas canvas = renderNode.start(width, height);
        //判断LayerType,以及获取HardwareLayer
        try {
            final HardwareLayer layer = getHardwareLayer();
            if (layer != null && layer.isValid()) {
                canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
            } else 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;
                //该View跳过绘制,则直接派发给子视图
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    dispatchDraw(canvas);
                } else {
                    draw(canvas);//绘制,包括绘制本身,修饰,以及派发,共六个步骤。
                }
            }
        } finally {
            renderNode.end(canvas);//绘制结束,保存canvas记录内容
            setDisplayListProperties(renderNode);
        }
    } else {
        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
}

三个步骤

第一步 RenderNode#start开始,创建DisplayListCanvas画布

DisplayListCanvas与底层的结构关系图。

DisplayListCanvas与底层结构关系图.jpg

每一个视图(View)都有一个画布,Canvas缓存池存储画布对象。

RenderNode#start方法。

public DisplayListCanvas start(int width, int height) {
    DisplayListCanvas canvas = DisplayListCanvas.obtain(this);
    canvas.setViewport(width, height);
    canvas.onPreDraw(null);
    return canvas;
}

DisplayListConvas继承Canvas。从缓存池获取,将start方法调用者RenderNode设置成Canvas内部RenderNode,表示此Canvas正被该视图使用,视图与RenderNode也是对应关系。

DisplayListCanvas#prepareDirty方法。

void DisplayListCanvas::prepareDirty(float left, float top,
        float right, float bottom) {
    mDisplayListData = new DisplayListData();
    mState.initializeSaveStack(0, 0, mState.getWidth(), 
                mState.getHeight(), Vector3());
    mDeferredBarrierType = kBarrier_InOrder;
    mState.setDirtyClip(false);
    mRestoreSaveCount = -1;
}

该方法准备将要绘制的脏区域,创建底层DisplayListData对象,CanvasState#initializeSaveStack初始化,创建Snapshot,初始化mRestoreSaveCount值为-1。mDeferredBarrierType值为kBarrier_InOrder,确保创建Chunk 。

总结
绘制架构包含RenderNode节点,DisplayListCanvas画布,底层DisplayListData对象,CanvasState状态存储对象,做完这些初始化工作,就可以在Java层画布上执行绘制操作方法啦,看第二步。

第二步 View#draw(canvas)方法,View绘制。

View的draw方法传入上面创建的DisplayListConvas,我们的视图有一些公有的绘制,例如背景,滚定条,修饰等。

@CallSuper
public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;//增加drawn标志
    ...
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);
        // Step 4, draw the children
        dispatchDraw(canvas);
        ...
        return;
    }
    ...
}

六步
1:绘制背景。
2:必要时存储canvas' layers,绘制边缘fade。
3:onDraw方法绘制视图内容,Canvas_api方法,自己实现。
4:dispatchDraw派发绘制子视图。
5:如有绘制fading edges,恢复canvas' layers。
6:绘制修饰,如滚动条。

下面主要分析第三、四步。
第三步,onDraw方法,重写,传入DisplayListConvas,drawXxxx方法。在View中是空方法,子类实现,不管子类是叶子节点还是容器视图,其主要目标是绘制自己。
一般情况下,容器视图的onDraw重写仅绘制一些边框,分割线之类的东西,而叶子节点视图的onDraw重写绘制成用户想要得到的视图。
第四步,dispatchDraw方法,派发绘制子视图内容,容器类视图重写,在View中是空方法。

总结:
onDraw方法自己重写,利用Canvas的drawXxx实现各种类型的绘制。View和ViewGroup有什么自己需要的绘制在这里完成。
View的dispatchDraw是空方法,不做任何操作。
ViewGroup重写dispatchDraw方法,实现子视图绘制派发。

ViewGroup#dispatchDraw方法。

@Override
protected void dispatchDraw(Canvas canvas) {
    for (int i = 0; i < childrenCount; i++) {
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                child.getAnimation() != null) {
            more |= drawChild(canvas, child, drawingTime);
        }
    }
}

遍历每一个子视图,drawChild触发子视图带三个参数的draw重载方法。

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

DecorView是容器视图,在dispatchDraw方法中,派发他的子视图的draw方法(三个参数重载)。

如何进入子视图updateDisplayListIfDirty中方法进行递归呢?
在draw三个参数重载方法中有一个判断,若是硬件渲染,触发子视图updateDisplayListIfDirty方法。

因此,每一个子视图均会递归到View#updateDisplayListIfDirty方法。进入此方法又回到和上面流程图一样的逻辑中,直到DecorView所有子视图绘制完成并且所有子视图Canvas结束,递归完成。最终,DecorView的Canvas结束。

总结
顶层视图绘制入口是draw(一个参数)方法,在draw(一个参数)中,派发每个子视图,子视图绘制入口是draw(三个参数),在draw(三个参数)中,触发子视图updateDisplayListIfDirty方法,子视图递归绘制。
因此,当走到第三步end方法时,表示视图本身以及子视图已经全部绘制完毕,也就是说当DecorView的RenderNode#end方准备执行时,所有draw已经完成。

若视图getHardwareLayer不空,如TextureView,触发Canvas#drawHardwareLayer,TextureView是View子类,不涉及绘制派发,相关知识后续研究。

View构造方法创建每一个视图的RenderNode。
每一个视图的RenderNode都会创建DisplayListCanvas。

第三步 RenderNode#end方法,DisplayListCanvas结束,保存数据。

RenderNode#end方法。

public void end(DisplayListCanvas canvas) {
    canvas.onPostDraw();
    long renderNodeData = canvas.finishRecording();
    nSetDisplayListData(mNativeRenderNode, renderNodeData);
    canvas.recycle();
    mValid = true;
}

三步

底层DisplayListCanvas#finishRecording方法。

DisplayListData* DisplayListCanvas::finishRecording() {
    ...
    DisplayListData* data = mDisplayListData;
    mDisplayListData = nullptr;
    mSkiaCanvasProxy.reset(nullptr);
    return data;
}

记录DisplayList数据结束,将RenderNode有效标志设置为true,如果没有完成,DisplayList无法显示。绘制结束后,视图RenderNode节点标明isValid有效标志。

总结

本文介绍硬件渲染绘制阶段上层基本流程。从ViewRootImpl开始,创建ThreadedRenderer对象,启用硬件渲染,利用ThreadedRenderer绘制,关键点在遍历每一个视图,根据视图RenderNode创建画布,有效绘制记录存储在RenderNode关联的底层DisplayListData
所谓绘制,为树形视图结构每一节点准备一个DisplayListCanvas,利用Canvas#drawXxx方法分别记录一些绘制操作,drawXxx包括画点,画圆,画矩形等操作。将这些操作存储在一个DisplayList集合中,这是App主线程负责的任务。


任重而道远

上一篇 下一篇

猜你喜欢

热点阅读