硬件渲染_树形视图节点绘制记录


视图树形结构,每一个节点均有Java层DisplayListCanvas和底层DisplayListCanvas。在Java层,调用Canvas#drawXxx方法,如drawPoint,drawPath,drawRect,DisplayListCanvas或Canvas中有JNI#方法,根据mNativeCanvasWrapper指针获取底层DisplayListCanvas。底层DisplayListCanvas继承底层Canvas。头文件定义在/frameworks/base/libs/hwui/Canvas.h
此外,除了绘制方法drawXxx,还有变换方法,如translate,scale等,以及save和restore方法,下面会通过一个绘图实例具体分析这些方法实现的操作。
前面已经介绍过,从顶层视图DecorView#updateDisplayListIfDirty方法开始绘制树形视图结构,为了分析简单,截取一小段树分支(虚线框内),通过下面视图结构,分析绘制时具体操作。

硬件渲染_树形视图节点绘制记录_第1张图片
树形节点绘制示例图.jpg
手机上显示的视图图片如下。
硬件渲染_树形视图节点绘制记录_第2张图片
手机上显示的视图图片.jpg

容器视图绘制

虚线框的树形结构截取如图。
硬件渲染_树形视图节点绘制记录_第3张图片
截取虚线框的视图.jpg

父视图0是LinearLayout,三个子视图分别是自定义CanvasView和两个TextView。
当遍历到达LinearLayout节点#updateDisplayListIfDirty方法时,再看一下此方法代码,如下。

public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
    // ThreadedRenderer是空,直接返回节点
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()//false,还未记录绘制
            || (mRecreateDisplayList)) {//重建Canvas
        ...//省略掉不需要重建Canvas的部分代码
        //第一次进来肯定需要建立Canvas,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的部分代码省略掉,LinearLayout不需要。
        try {
            // 一般视图走硬件渲染都执行下面程序
            computeScroll();
            canvas.translate(-mScrollX, -mScrollY);
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                //LinearLayout视图会跳过绘制,则直接派发给子视图
                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;
}

LinearLayout视图创建画布DisplayListCanvas后,因有PFLAG_SKIP_DRAW标志,选择跳过绘制,它没有设置Background,在View框架源码中会设置此标志。跳过绘制,即不会走LinearLayout的onDraw方法,另外四个步骤都不会触发,仅仅触发父类ViewGroup的dispatchDraw方法。绘制直接向子视图派发。
ViewGroup#dispatchDraw方法。

@Override
protected void dispatchDraw(Canvas canvas) {
    ...
    boolean more = false;
    final long drawingTime = getDrawingTime();
    //LinearLayout的画布写入Reorder栅栏 
    if (usingRenderNodeProperties) canvas.insertReorderBarrier();
    ...
    //绘制子视图
    for (int i = 0; i < childrenCount; i++) {
        ...
        int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
        final View child = (preorderedList == null)
                    ? children[childIndex] : preorderedList.get(childIndex);
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            more |= drawChild(canvas, child, drawingTime);
        }
      }
    ...
    //LinearLayout的画布写入Inorder栅栏 
    if (usingRenderNodeProperties) canvas.insertInorderBarrier();
    ...
}
//drawChild方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

dispatchDraw源码比较多,主要功能是派发子视图绘制。触发每个子视图三个参数的draw重载方法。在遍历子视图前后,有两个方法,LinearLayout画布的insertReorderBarrier和insertInorderBarrier方法。
他们在触发底层DisplayListCanvas的方法一样,只是enableReorder标志不同,insertReorderBarrier支持重排。

void DisplayListCanvas::insertReorderBarrier(bool enableReorder) {
    flushRestoreToCount();
    flushTranslate();
    mDeferredBarrierType = enableReorder ? kBarrier_OutOfOrder : kBarrier_InOrder;
}

栅栏设置成kBarrier_OutOfOrder或kBarrier_InOrder类型,kBarrier_OutOfOrder用于标记Chunk的一个变量值reorderChildren。DisplayListCanvas的prepareDirty方法初始化值默认是kBarrier_InOrder,这个值后续addOpAndUpdateChunk方法会用到,在绘制子视图之前,已经创建过第一个Chunk。
在绘制子视图前,插入一个kBarrier_OutOfOrder栅栏,LinearLayout画布drawXxx方法绘制会创建一个新Chunk,该Chunk索引LinearLayout画布绘制的子视图节点。绘制子视图结束后,再次插入一个kBarrier_InOrder栅栏,再次新建一个Chunk块,索引LinearLayout画布后续绘制的内容。

LinearLayout子视图是叶子节点,子视图绘制,三个参数的draw重载方法。
View#draw方法。

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
    boolean drawingWithRenderNode = mAttachInfo != null
            && mAttachInfo.mHardwareAccelerated
            && hardwareAcceleratedCanvas;
    boolean more = false;
    ...
    RenderNode renderNode = null;
    Bitmap cache = null;
    ...
    //硬件渲染
    if (drawingWithRenderNode) {
        //在这里触发子视图渲染方法。
        renderNode = updateDisplayListIfDirty();
        if (!renderNode.isValid()) {
            renderNode = null;
            drawingWithRenderNode = false;
        }
    }
    ...
    if (!drawingWithDrawingCache) {
        if (drawingWithRenderNode) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            //父视图画布绘制子视图RenderNode节点
            ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
        } else {
            ...
        }
    } else if (cache != null) {
        ...
    }

    if (restoreTo >= 0) {
        canvas.restoreToCount(restoreTo);
    }
    ..
    mRecreateDisplayList = false;

    return more;
}

draw方法很长,这里指摘取硬件渲染相关的。关注两个点。
1:子视图#updateDisplayListIfDirty方法,重建Canvas,传递给一个参数的draw重载方法,绘制,返回子视图RenderNode节点,该节点相关画布已经完成绘制内容记录。
2:LinearLayout画布drawRenderNode绘制子视图RenderNode节点。

void DisplayListCanvas::drawRenderNode(RenderNode* renderNode) {
    DrawRenderNodeOp* op = new (alloc()) DrawRenderNodeOp(
            renderNode,
            *mState.currentTransform(),
            mState.clipIsSimple());
    addRenderNodeOp(op);
}

创建一个DrawRenderNodeOp,DrawRenderNodeOp继承DrawBoundedOp,DrawBoundedOp继承DrawOp,基类是DisplayListOp。

size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) {
    //增加一个绘制操作到mDisplayListData的displayListOps数组。
    int opIndex = addDrawOp(op);
    //mDisplayListData是LinearLayout底层存储绘制数据的对象
    //增加操作到DisplayListData的mChildren数组,代表子视图
    int childIndex = mDisplayListData->addChild(op);
    //写入了一个绘制子节点的操作,告诉Chunk的endChildIndex自增。
    DisplayListData::Chunk& chunk = mDisplayListData->chunks.editTop();
    chunk.endChildIndex = childIndex + 1;

    if (op->renderNode()->stagingProperties().isProjectionReceiver()) {
        mDisplayListData->projectionReceiveIndex = opIndex;
    }
    return opIndex;
}

addDrawOp方法增加一个绘制Op,后面会详细介绍,总之,将DrawRenderNodeOp加入到DisplayListData的displayListOps数组中。
addChild方法将操作加入到DisplayListData的mChildren数组中。
注意,因前期插入一个kBarrier_OutOfOrder栅栏,因此在addDrawOp触发的addOpAndUpdateChunk中,会创建一个新Chunk块,写入DrawRenderNodeOp后,更新该Chunk中指向mChildren数组的endChildIndex 索引。
三个叶子RenderNode节点操作写入LinearLayout底层数据数组中,他们在一个Chunk块中。子视图绘制完毕,再次插入一个kBarrier_InOrder栅栏,创建一个新Chunk。

硬件渲染_树形视图节点绘制记录_第4张图片
父视图LinearLayout绘制时Chunk与记录.jpg

叶子节点视图绘制

叶子节点绘制主要关注onDraw方法,CanvasView的onDraw方法,绘制三个矩形区域。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.parseColor("#ffffcc")); //画布颜色
    Paint paint = new Paint();
    paint.setStyle(Paint.Style.STROKE);//设置非填充风格
    Rect rect1 = new Rect(20, 20, 180, 180); 
    canvas.drawRect(rect1, paint);//绘制一个正方形
    /************************第一层*****************************/
    canvas.save();//当前状态保存
    /***********************第二层*****************************/
    canvas.translate(180, 180);
    Rect rect2 = new Rect(20, 20, 180, 180); 
    canvas.drawRect(rect2, paint);//再次绘制一个正方形
    /************************第二层***************************/
    canvas.restore();//恢复到第一层状态
    /************************第一层**************************/
    Rect rect3 = new Rect(20, 200, 180, 360); 
    canvas.drawRect(rect3, paint);//再次绘制一个正方形
}

四个Canvas方法

drawRect,绘制一个矩形区域。
save,保存当前图层状态。
restore,恢复上一个图层状态。
translate,两个方向x和y,偏移一定距离。

先绘制一个矩形,然后保存状态,进行偏移,绘制第二个矩形,第二个矩形受到偏移影响,恢复状态,绘制第三个矩形,第三个矩形不受偏移影响。

详细分析

执行顺序
drawRect(1)
save
translate
drawRect(2)
restore
drawRect(3)

1,drawRect:触发底层DisplayListCanvas的drawRect方法。该方法是
在DisplayListCanvas的头文件中定义虚函数,在Canvas的头文件中也有定义。
底层 DisplayListCanvas#drawRect方法。

void DisplayListCanvas::drawRect(float left, float top, float right, float bottom,
        const SkPaint& paint) {
    addDrawOp(new (alloc()) DrawRectOp(left, top, right, bottom, refPaint(&paint)));
}

addDrawOp增加一个绘制Op,DrawRectOp类型,不同的绘制Op不同,如其drawPoints绘制增加的Op是DrawPointsOp类型,总之,他们都继承DrawOp类。

addDrawOp增加一个绘制操作DrawOp。DrawOp继承DisplayListOp类。

底层DisplayListCanvas#addDrawOp方法。

size_t DisplayListCanvas::addDrawOp(DrawOp* op) {
    Rect localBounds;
    ...
    mDisplayListData->hasDrawOps = true;
    return flushAndAddOp(op);
}

触发flushAndAddOp方法。
2,save:触发底层DisplayListCanvas的save方法。注意:save和restore一般是成对出现的。
底层DisplayListCanvas#save方法。

int DisplayListCanvas::save(SkCanvas::SaveFlags flags) {
    addStateOp(new (alloc()) SaveOp((int) flags));
    return mState.save((int) flags);
}

addStateOp增加状态操作StateOp,前面的是增加DrawOp,而现在是StateOp

和DrawOp一样,状态StateOp也继承DisplayListOp类。

底层DisplayListCanvas#addStateOp方法。

size_t DisplayListCanvas::addStateOp(StateOp* op) {
    return flushAndAddOp(op);
}

触发flushAndAddOp方法。
除了flushAndAddOp保存,还会触发CanvasState保存。

总结:
DisplayListCanvas的save方法,addStateOp增加一个SaveOp,addStateOp方法和addDrawOp类似,都触发flushAndAddOp方法,入参是同一个基类DisplayListOp。

CanvasState#save方法。

int CanvasState::save(int flags) {
    return saveSnapshot(flags);
}

int CanvasState::saveSnapshot(int flags) {
    mSnapshot = new Snapshot(mSnapshot, flags);
    return mSaveCount++;
}

在CanvasState类save操作中,创建一个Snapshot对象,封装当前对象,新建的Snapshot放到链表最前面,mSaveCount自增,mSaveCount代表当前操作的是位于第几层次,若上层调用了restore,则触发CanvasState的restore方法,回退到上一层。

可以看出,每一个层次状态的save都保存在Snapshot中。save之后的改变在新建的Snapshot保存,restore后回到之前的Snapshot。

3,translate:触发底层DisplayListCanvas的translate方法,x和y轴方向上的偏移。
底层DisplayListCanvas#translate方法

void DisplayListCanvas::translate(float dx, float dy) {
    if (dx == 0.0f && dy == 0.0f) return;

    mHasDeferredTranslate = true;
    mTranslateX += dx;
    mTranslateY += dy;
    flushRestoreToCount();
    mState.translate(dx, dy, 0.0f);
}

增加偏移量dx和dy。
flushRestoreToCount方法,在mRestoreSaveCount>=0时才起作用,即执行过restore或restoreToCount。
写入一个RestoreToCountOp操作。
CanvasState的translate方法,当前mSnapshot的transform触发translate偏移。因前面触发过一个save方法,因此有两个Snapshot,当前mSnapshot是表头第一个。

4,restore:触发底层DisplayListCanvas的restore方法,恢复到上一个绘制状态。
底层DisplayListCanvas#restore方法。

void DisplayListCanvas::restore() {
    if (mRestoreSaveCount < 0) {
        restoreToCount(getSaveCount() - 1);
        return;
    }

    mRestoreSaveCount--;
    flushTranslate();
    mState.restore();
}

若mRestoreSaveCount已经>=0,则直接自减。每次restore只能回退一层。同时CanvasState回退,内部mSaveCount保存层级。
若mRestoreSaveCount小于0,即有可能是初始值-1,则触发restoreToCount方法,getSaveCount获取当前层级。若执行过一个save,restore时,getSaveCount得到2,那么restoreToCount恢复的值就是1,mRestoreSaveCount设置成1。

总结:restore回退一层。

上层直接调用restoreToCount方法时,直接回退saveCount层,这个方法中会设置mRestoreSaveCount值。

底层DisplayListCanvas#restoreToCount方法。

void DisplayListCanvas::restoreToCount(int saveCount) {
    mRestoreSaveCount = saveCount;
    flushTranslate();
    mState.restoreToCount(saveCount);
}

save和restore成对出现,多个save后,第一次restore时,mRestoreSaveCount将会是初始值-1,restoreToCount方法会第一次设置其值,设置成restore回退后的层级。
即mRestoreSaveCount存储当前层级。

例如,3个save后,第一次restore后,则mRestoreSaveCount变为3。

总结:
在上层一次save,将当前的状态保存下来,包括偏移,缩放等状态,后续的改变仍然在此基础上,偏移,绘制等,若执行restore,则回到上一个save保存的状态中,中间的改变忽略掉。

硬件渲染_树形视图节点绘制记录_第5张图片
硬件渲染节点绘制写入流程.jpg

1:drawRect,绘制正方形,第一层,写入DrawRectOp,mRestoreSaveCount是-1,flushRestoreCount无作用,无偏移,flushTranslate无作用。
addOpAndUpdateChunk写入绘制。
2:save,保存当前层级状态,mRestoreSaveCount是-1,flushRestoreCount无作用,无偏移,flushTranslate无作用。
addOpAndUpdateChunk写入状态。
3:translate,偏移,第二层,mRestoreSaveCount是-1,flushRestoreCount无作用,设置偏移值mTranslateX与mTranslateY,当前Snapshot(第二层)偏移。
4:drawRect,绘制正方形,第二层,写入DrawRectOp,mRestoreSaveCount是-1,flushRestoreCount无作用,有偏移,
addOpAndUpdateChunk写入TranslateOp,并恢复偏移值。
addOpAndUpdateChunk写入绘制。

5:restore,恢复上一个层级,前面仅有一个save,因此mRestoreSaveCount是-1,触发restoreToCount方法,设置mRestoreSaveCount=1。无偏移,flushTranslate无作用。
6:drawRect,绘制正方形,第一层,写入DrawRectOp,mRestoreSaveCount是1,无偏移,flushTranslate无作用,
addOpAndUpdateChunk写入RestoreToCountOp。并恢复mRestoreSaveCount为-1。
addOpAndUpdateChunk写入绘制。

flushAndAddOp方法在上层写入DrawOp或StateOp时触发。
最终目的是addOpAndUpdateChunk。
当前存在mRestoreSaveCount>=0或偏移,首先写入相应Op,即RestoreToCountOp与TranslateOp。

底层DisplayListCanvas#flushAndAddOp方法。

size_t DisplayListCanvas::flushAndAddOp(DisplayListOp* op) { //
    flushRestoreToCount();
    flushTranslate();
    return addOpAndUpdateChunk(op);
}

三个方法。
flushRestoreToCount:写入RestoreToCountOp。
flushTranslate:写入TranslateOp。
addOpAndUpdateChunk:执行写入。

DisplayListCanvas#flushRestoreToCount方法。

void DisplayListCanvas::flushRestoreToCount() {
    if (mRestoreSaveCount >= 0) {
        addOpAndUpdateChunk(new (alloc()) RestoreToCountOp(mRestoreSaveCount));
        mRestoreSaveCount = -1;
    }
}

若mRestoreSaveCount>=0,增加一个RestoreToCountOp操作。mRestoreSaveCount在刚初始化时prepareDirty,设置成-1。
DisplayListCanvas#flushTranslate方法。

void DisplayListCanvas::flushTranslate() {
    if (mHasDeferredTranslate) {
        if (mTranslateX != 0.0f || mTranslateY != 0.0f) {
            addOpAndUpdateChunk(new (alloc()) TranslateOp(mTranslateX, mTranslateY));
            mTranslateX = mTranslateY = 0.0f;
        }
        mHasDeferredTranslate = false;
    }
}

mTranslateX或mTranslateY不是0时写入TranslateOp。

addOpAndUpdateChunk方法负责增加Op,不仅包括RestoreToCountOp和TranslateOp。

DisplayListCanvas#addOpAndUpdateChunk方法。

size_t DisplayListCanvas::addOpAndUpdateChunk(DisplayListOp* op) {//增加op,更新chunk
    int insertIndex = mDisplayListData->displayListOps.add(op);
    if (mDeferredBarrierType != kBarrier_None) {
        // op is first in new chunk
        mDisplayListData->chunks.push();
        DisplayListData::Chunk& newChunk = mDisplayListData->chunks.editTop();//新Chunk
        newChunk.beginOpIndex = insertIndex;
        newChunk.endOpIndex = insertIndex + 1;
        newChunk.reorderChildren = (mDeferredBarrierType == kBarrier_OutOfOrder);

        int nextChildIndex = mDisplayListData->children().size();
        newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
        mDeferredBarrierType = kBarrier_None;
    } else {
        // standard case - append to existing chunk
        mDisplayListData->chunks.editTop().endOpIndex = insertIndex + 1;//最上面Chunk修改
    }
    return insertIndex;
}

将操作DisplayListOp加入DisplayListData的内部数组,类型全部都是DisplayListOp,返回插入的索引。
DisplayListData在底层Canvas的prepareDirty时创建,不管是绘制操作还是状态操作,都属于DisplayListOp类型,按照顺序放到相同数组。

上述例子中DisplayListData内部数组的内容如下图所示。
硬件渲染_树形视图节点绘制记录_第6张图片
DisplayListData内部数组写入的操作.jpg

依次是DrawRectOp,SaveOp,TranslateOp,DrawRectOp,RestoreToCountOp,DrawRectOp。


任重而道远

你可能感兴趣的:(硬件渲染_树形视图节点绘制记录)