Android8.0硬件加速的重绘流程

几个PFLAG的作用

  • PFLAG_DRAW_ANIMATION:表示当前view在做Animation动画。
  • PFLAG_HAS_BOUNDS:表示此view是否layout过。
  • PFLAG_DRAWN :当invalidate时会把此标记删除,当调用draw方法(包括软件硬件两个都设置了),
  • PFLAG_DRAWING_CACHE_VALID: 表示当前cache是否有效,

如果父view是soft layer,那么在把绘制分发到此view时,会用这个flag来判断是否进行重建drawing cache,不管该view的layerType是hard/soft。

 

  • PFLAG_INVALIDATED :指示当前view明确是invalidated,而不只是因为其子view invalidate而dirty。该标志用于确定何时需要重新创建view的 display list(而不是仅仅返回对其现有 display list的引用)。

好像软件绘制没有判断PFLAG_INVALIDATED的,但在硬件加速中view.mRecreateDisplayList是根据此标记来设置。

 

  • PFLAG_DIRTY:View flag indicating whether this view was invalidated (fully or partially.)

当前view调用了invalidate,或当前view的子view调用了invalidate,都会是的当前view加上此flag。

 

软件绘制

是基于Android8.0的源码来分析的。

 

前提是window是关闭了硬件加速。

 

实际阅读源码并实验,得出通常情况下的软件绘制刷新逻辑:

 

1. 默认情况下,View的clipChildren属性为true,即每个View绘制区域不能超出其父View的范围。如果设置一个页面根布局的clipChildren属性为false,则子View可以超出父View的绘制区域。

2. 当一个View触发invalidate,且没有播放动画、没有触发layout的情况下:

1. 对于全不透明的View,其自身会设置标志位PFLAG_DIRTY,其父View会设置标志位PFLAG_DIRTY_OPAQUE。在draw(canvas)方法中,只有这个View自身重绘。
2. 对于可能有透明区域的View,其自身和父View都会设置标志位PFLAG_DIRTY。
1. clipChildren为true时,脏区会被转换成ViewRoot中的Rect,刷新时层层向下判断,当View与脏区有重叠则重绘。如果一个View超出父View范围且与脏区重叠,但其父View不与脏区重叠,这个子View不会重绘。

2. clipChildren为false时,ViewGroup.invalidateChildInParent()中会把脏区扩大到自身整个区域,于是与这个区域重叠的所有View都会重绘。

 

硬件加速

是基于Android8.0的源码来分析的。

硬件加速下LAYER_TYPE_NONE/LAYER_TYPE_HARDWARE

调用不会导致invalidate的方法流程

以setTranslationX为例

public void setTranslationX(float translationX) {
    if (translationX != getTranslationX()) {
        invalidateViewProperty(true, false);
        mRenderNode.setTranslationX(translationX);
        invalidateViewProperty(false, true);

        invalidateParentIfNeededAndWasQuickRejected();
        notifySubtreeAccessibilityStateChangedIfNeeded();
    }
}

 

可以看到setTranslationX中会把translationX设置到mRenderNode中。

void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
    if (!isHardwareAccelerated()
            || !mRenderNode.isValid()
            || (mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
        if (invalidateParent) {
            invalidateParentCaches();
        }
        if (forceRedraw) {
            mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
        }
        invalidate(false);
    } else {
        damageInParent();
    }
}

由于是在硬件加速并且没有在做Animation动画,所以会调用damageInParent()。

protected void damageInParent() {
    if (mParent != null && mAttachInfo != null) {
        mParent.onDescendantInvalidated(this, this);
    }
}

onDescendantInvalidated的两个参数:child表示包含target的当前view的直接子view,target表示引发向上遍历的view。

public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
    /*
     * HW-only, Rect-ignoring damage codepath
     *
     * We don't deal with rectangles here, since RenderThread native code computes damage for
     * everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)
     */

    // if set, combine the animation flag into the parent
    mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);

    if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
        // We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
        // optimization in provides in a DisplayList world.
        mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;

        // simplified invalidateChildInParent behavior: clear cache validity to be safe...
        mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
    }

    // ... and mark inval if in software layer that needs to repaint (hw handled in native)
    if (mLayerType == LAYER_TYPE_SOFTWARE) {
        // Layered parents should be invalidated. Escalate to a full invalidate (and note that
        // we do this after consuming any relevant flags from the originating descendant)
        mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
        target = this;
    }

    if (mParent != null) {
        mParent.onDescendantInvalidated(this, target);
    }
}

由上可知target的祖先view的mPrivateFlags中都加上了PFLAG_DIRTY,去掉了PFLAG_DRAWING_CACHE_VALID。

 

最终调用到了ViewRootImpl中的onDescendantInvalidated,并安排了一次Traversals:

public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
    if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
        mIsAnimating = true;
    }
    invalidate();
}

void invalidate() {
    mDirty.set(0, 0, mWidth, mHeight);
    if (!mWillDrawSoon) {
        scheduleTraversals();
    }
}

接下来就是draw的过程

ViewRootImpl.draw的过程会调用ThreadedRenderer.draw

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

private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    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;
}

由于targetView的祖先view都加上了PFLAG_DIRTY,去掉了PFLAG_DRAWING_CACHE_VALID,也没有加入View.PFLAG_INVALIDATED。所以view.mRecreateDisplayList为false。

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

    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()
            || (mRecreateDisplayList)) {
        // Don't need to recreate the display list, just need to tell our
        // children to restore/recreate theirs
        if (renderNode.isValid()
                && !mRecreateDisplayList) {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchGetDisplayList();

            return renderNode; // no work needed
        }
    ...
}

此时调用是处在target的祖先view的updateDisplayListIfDirty中。由于targetView的祖先都没有PFLAG_DRAWING_CACHE_VALID,所以肯定是能进入的。

接着显然于targetView的祖先view都满足renderNode.isValid() && !mRecreateDisplayList,所以接着调用dispatchGetDisplayList后直接返回。

 

下边看dispatchGetDisplayList:

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

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

如果此时child还是target的祖先view,那么child.mRecreateDisplayList还是false。

如果此时child就是target,那么他的mRecreateDisplayList也是false,因为target在setTranslationX过程中并没有调用invalidate。

 

那么最终调用target的updateDisplayListIfDirty,

public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
    if (!canHaveDisplayList()) {
        // can't populate RenderNode, don't try
        return renderNode;
    }

    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()
            || (mRecreateDisplayList)) {
        // Don't need to recreate the display list, just need to tell our
        // children to restore/recreate theirs
        if (renderNode.isValid()
                && !mRecreateDisplayList) {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchGetDisplayList();

            return renderNode; // no work needed
        }

        ...

    } else {
        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
}

由于target的PFLAG_DRAWING_CACHE_VALID并没被去掉,且renderNode.isValid为true,mRecreateDisplayList也为false,所以会跳到下边的else逻辑中,然后就直接返回。

 

可以看到整个过程是非常高效的,虽然进行了从上到下的遍历,但并没有进行任何的重绘或重建displaylist。

那既然什么都没做,为什么要进行从上到下的遍历一次?

 

调用会导致invalidate的方法流程

invalidate流程

public void invalidate() {
    invalidate(true);
}

public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
    ...
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
            || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
            || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
            || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
        if (fullInvalidate) {
            mLastIsOpaque = isOpaque();
            mPrivateFlags &= ~PFLAG_DRAWN;
        }

        mPrivateFlags |= PFLAG_DIRTY;

        if (invalidateCache) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        // Propagate the damage rectangle to the parent view.
        final AttachInfo ai = mAttachInfo;
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            p.invalidateChild(this, damage);
        }

        ...
    }
}

由代码可知:

mPrivateFlags |= PFLAG_DIRTY;

mPrivateFlags |= PFLAG_INVALIDATED;

mPrivateFlags &= ~PFLAG_DRAWN;

mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

重绘之前多次调用的话,除了第一次有用,之后就不会做任何事。

 

接着调用parentView的invalidateChild

public final void invalidateChild(View child, final Rect dirty) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null && attachInfo.mHardwareAccelerated) {
        // HW accelerated fast path
        onDescendantInvalidated(child, child);
        return;
    }
    ...
}

由于是硬件加速所以会直接进入onDescendantInvalidated逻辑

public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
    /*
     * HW-only, Rect-ignoring damage codepath
     *
     * We don't deal with rectangles here, since RenderThread native code computes damage for
     * everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)
     */

    // if set, combine the animation flag into the parent
    mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);

    if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
        // We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
        // optimization in provides in a DisplayList world.
        mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;

        // simplified invalidateChildInParent behavior: clear cache validity to be safe...
        mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
    }

    // ... and mark inval if in software layer that needs to repaint (hw handled in native)
    if (mLayerType == LAYER_TYPE_SOFTWARE) {
        // Layered parents should be invalidated. Escalate to a full invalidate (and note that
        // we do this after consuming any relevant flags from the originating descendant)
        mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
        target = this;
    }

    if (mParent != null) {
        mParent.onDescendantInvalidated(this, target);
    }
}

之后的流程 和 不会导致invalidate的流程一样,target的祖先view的mPrivateFlags中都加上了PFLAG_DIRTY,去掉了PFLAG_DRAWING_CACHE_VALID。

直接看绘制流程

 

绘制流程

开始时和不会导致invalidate的流程一样,直到调用到recreateChildDisplayList里的child为target时就开始重建流程:

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

由于target的mPrivateFlags:

  • mPrivateFlags |= PFLAG_DIRTY;
  • mPrivateFlags |= PFLAG_INVALIDATED;
  • mPrivateFlags &= ~PFLAG_DRAWN;
  • mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

所以mRecreateDisplayList 为true。接着就会调用target的updateDisplayListIfDirty方法,会走下边的流程,创建一个DisplayListCanvas,调用draw(canvas), 接着调用onDraw,来重建该target的displaylist。

public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
    if (!canHaveDisplayList()) {
        // can't populate RenderNode, don't try
        return renderNode;
    }

    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()
            || (mRecreateDisplayList)) {

//由于mRecreateDisplayList 为true,所以直接跳过此步骤,进行重建displaylist
        // Don't need to recreate the display list, just need to tell our
        // children to restore/recreate theirs
        if (renderNode.isValid()
                && !mRecreateDisplayList) {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            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 DisplayListCanvas canvas = renderNode.start(width, height);
        canvas.setHighContrastText(mAttachInfo.mHighContrastText);

        try {
            if (layerType == LAYER_TYPE_SOFTWARE) {
                buildDrawingCache(true);
                Bitmap cache = getDrawingCache(true);
                if (cache != null) {
                    canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                }
            } else {
//由于是硬件加速非soft layer所以进入此处
                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.end(canvas);
            setDisplayListProperties(renderNode);
        }
    } else {
        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
}

可以看到整个过程是非常高效的,虽然进行了从上到下的遍历,但只有target进行了重绘或重建displaylist,其他view不会执行任何绘制操作。

 

如果invalidate的target是ViewGroup的重绘流程

上边的target是一个view,如果invalidate的target是ViewGroup的话,

接着上边的updateDisplayListIfDirty,ViewGroup最终会调用dispatchDraw,会把绘制分发给子view

protected void dispatchDraw(Canvas canvas) {
    boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    int flags = mGroupFlags;
    
    ...

    int clipSaveCount = 0;
    final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
    if (clipToPadding) {
        clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
        canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                mScrollX + mRight - mLeft - mPaddingRight,
                mScrollY + mBottom - mTop - mPaddingBottom);
    }

    // We will draw our child's animation, let's reset the flag
    mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
    mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;

    boolean more = false;
    final long drawingTime = getDrawingTime();

    if (usingRenderNodeProperties) canvas.insertReorderBarrier();
    final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
    int transientIndex = transientCount != 0 ? 0 : -1;
    // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
    // draw reordering internally
    final ArrayList<View> preorderedList = usingRenderNodeProperties ? null : buildOrderedChildList();
    final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
    for (int i = 0; i < childrenCount; i++) {

    ...

        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    ...
}

 

 

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

 

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
    /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
     *
     * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
     * HW accelerated, it can't handle drawing RenderNodes.
     */
    boolean drawingWithRenderNode = mAttachInfo != null
            && mAttachInfo.mHardwareAccelerated
            && hardwareAcceleratedCanvas;

    boolean more = false;
    final boolean childHasIdentityMatrix = hasIdentityMatrix();
    final int parentFlags = parent.mGroupFlags;

    if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {
        parent.getChildTransformation().clear();
        parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
    }
    ...
    
    if (hardwareAcceleratedCanvas) {
        // Clear INVALIDATED flag to allow invalidation to occur during rendering, but
        // retain the flag's value temporarily in the mRecreateDisplayList flag
        mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
        mPrivateFlags &= ~PFLAG_INVALIDATED;
    }

    RenderNode renderNode = null;
    Bitmap cache = null;

//soft绘制
    int layerType = getLayerType(); // TODO: signify cache state with just 'cache' local
    if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
         if (layerType != LAYER_TYPE_NONE) {
             // If not drawing with RenderNode, treat HW layers as SW
             layerType = LAYER_TYPE_SOFTWARE;
             buildDrawingCache(true);
        }
        cache = getDrawingCache(true);
    }

//硬件绘制
    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 (!renderNode.isValid()) {
            // Uncommon, but possible. If a view is removed from the hierarchy during the call
            // to getDisplayList(), the display list will be marked invalid and we should not
            // try to use it again.
            renderNode = null;
            drawingWithRenderNode = false;
        }
    }

    int sx = 0;
    int sy = 0;
    if (!drawingWithRenderNode) {
        computeScroll();
        sx = mScrollX;
        sy = mScrollY;
    }

    final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
    final boolean offsetForScroll = cache == null && !drawingWithRenderNode;

    int restoreTo = -1;
    if (!drawingWithRenderNode || transformToApply != null) {
        restoreTo = canvas.save();
    }
    if (offsetForScroll) {
        canvas.translate(mLeft - sx, mTop - sy);
    } else {
        if (!drawingWithRenderNode) {
            canvas.translate(mLeft, mTop);
        }
        if (scalingRequired) {
            if (drawingWithRenderNode) {
                // TODO: Might not need this if we put everything inside the DL
                restoreTo = canvas.save();
            }
            // mAttachInfo cannot be null, otherwise scalingRequired == false
            final float scale = 1.0f / mAttachInfo.mApplicationScale;
            canvas.scale(scale, scale);
        }
    }

    float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
    ...alpah...

    ...

    if (!drawingWithDrawingCache) {
        if (drawingWithRenderNode) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
        } else {
            // Fast path for layouts with no backgrounds
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                dispatchDraw(canvas);
            } else {
                draw(canvas);
            }
        }
    } else if (cache != null) {
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        ...
    }

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


    mRecreateDisplayList = false;

    return more;
}

 

最后把子view的renderNode绘制到父view的DisplayListCanvas上。

 

如果在子view中调用了invalidateParent的方法,那么就只会让父view进行重绘操作(当然前提是子view没有invalidate)。

 

硬件加速下LAYER_TYPE_SOFTWARE

调用不会导致invalidate的方法流程

以setTranslationX为例

public void setTranslationX(float translationX) {
    if (translationX != getTranslationX()) {
        invalidateViewProperty(true, false);
        mRenderNode.setTranslationX(translationX);
        invalidateViewProperty(false, true);

        invalidateParentIfNeededAndWasQuickRejected();
        notifySubtreeAccessibilityStateChangedIfNeeded();
    }
}

可以看到setTranslationX中会把translationX设置到mRenderNode中。

void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
    if (!isHardwareAccelerated()
            || !mRenderNode.isValid()
            || (mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
        if (invalidateParent) {
            invalidateParentCaches();
        }
        if (forceRedraw) {
            mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
        }
        invalidate(false);
    } else {
        damageInParent();
    }
}

 

因为当前的window还是硬件加速,mRenderNode.isValid()也为true,且没有处于Animation动画中,所以还是会进入damageInParent()逻辑。

protected void damageInParent() {
    if (mParent != null && mAttachInfo != null) {
        mParent.onDescendantInvalidated(this, this);
    }
}

public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
    /*
     * HW-only, Rect-ignoring damage codepath
     *
     * We don't deal with rectangles here, since RenderThread native code computes damage for
     * everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)
     */

    // if set, combine the animation flag into the parent
    mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);

    if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
        // We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
        // optimization in provides in a DisplayList world.
        mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;

        // simplified invalidateChildInParent behavior: clear cache validity to be safe...
        mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
    }

    // ... and mark inval if in software layer that needs to repaint (hw handled in native)
    if (mLayerType == LAYER_TYPE_SOFTWARE) {
        // Layered parents should be invalidated. Escalate to a full invalidate (and note that
        // we do this after consuming any relevant flags from the originating descendant)
        mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
        target = this;
    }

    if (mParent != null) {
        mParent.onDescendantInvalidated(this, target);
    }
}

 

由上可知祖先view的mPrivateFlags:

  • mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
  • mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

 

同时如果祖先view中如果也有LAYER_TYPE_SOFTWARE,

  • 那么就也会把此view的mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY,
  • 同时把target变为该view。

 

之后的逻辑完全和调用不会导致invalidate的方法流程一样。

最终调用到了ViewRootImpl中的onDescendantInvalidated,并安排了一次Traversals

 

绘制流程

硬件加速下调用不会导致invalidate的方法的 绘制流程 完全一样。

虽然进行了从上到下的遍历,但并没有进行任何的重绘或重建displaylist。

 

调用会导致invalidate的方法流程

invalidate流程

硬件加速下调用会导致invalidate的方法流程 完全一样。

 

绘制流程

开始时和不会导致invalidate的流程一样,直到调用到recreateChildDisplayList里的child为target时就开始重建流程:

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

由于target的mPrivateFlags:

  • mPrivateFlags |= PFLAG_DIRTY;
  • mPrivateFlags |= PFLAG_INVALIDATED;
  • mPrivateFlags &= ~PFLAG_DRAWN;
  • mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

所以mRecreateDisplayList 为true。

 

public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
    if (!canHaveDisplayList()) {
        // can't populate RenderNode, don't try
        return renderNode;
    }

    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()
            || (mRecreateDisplayList)) {

//由于mRecreateDisplayList 为true,所以直接跳过此步骤,进行重建displaylist
        // Don't need to recreate the display list, just need to tell our
        // children to restore/recreate theirs
        if (renderNode.isValid()
                && !mRecreateDisplayList) {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            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 DisplayListCanvas canvas = renderNode.start(width, height);
        canvas.setHighContrastText(mAttachInfo.mHighContrastText);

        try {
            if (layerType == LAYER_TYPE_SOFTWARE) {
//由于是LAYER_TYPE_SOFTWARE所以进入此处
                buildDrawingCache(true);
                Bitmap cache = getDrawingCache(true);
                if (cache != null) {
                    canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                }
            } else {
...
            }
        } finally {
            renderNode.end(canvas);
            setDisplayListProperties(renderNode);
        }
    } else {
        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
}

然后调用buildDrawingCache(true);来绘制软件drawing cache,即绘制的内容都保存为bitmap上。
最后canvas.drawBitmap(cache, 0, 0, mLayerPaint);把bitmap cache绘制到了DisplayListCanvas 上。

 

可以看到整个过程是非常高效的,虽然进行了从上到下的遍历,但只对target进行了重绘或重建drawing cache(bitmap)。

 

如果invalidate的target是ViewGroup的重绘流程

接着上边的updateDisplayListIfDirty,里边最后调用到buildDrawingCache(true);

public void buildDrawingCache(boolean autoScale) {
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ? mDrawingCache == null : mUnscaledDrawingCache == null)) {
        try {
            buildDrawingCacheImpl(autoScale);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
}

private void buildDrawingCacheImpl(boolean autoScale) {
    mCachingFailed = false;

    int width = mRight - mLeft;
    int height = mBottom - mTop;

    ...

    boolean clear = true;
    Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;

    if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
        Bitmap.Config quality;
        if (!opaque) {
            // Never pick ARGB_4444 because it looks awful
            // Keep the DRAWING_CACHE_QUALITY_LOW flag just in case
            switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {
                case DRAWING_CACHE_QUALITY_AUTO:
                case DRAWING_CACHE_QUALITY_LOW:
                case DRAWING_CACHE_QUALITY_HIGH:
                default:
                    quality = Bitmap.Config.ARGB_8888;
                    break;
            }
        } else {
            // Optimization for translucent windows
            // If the window is translucent, use a 32 bits bitmap to benefit from memcpy()
            quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
        }

        // Try to cleanup memory
        if (bitmap != null) bitmap.recycle();

        try {
            bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
                    width, height, quality);
            bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
            if (autoScale) {
                mDrawingCache = bitmap;
            } else {
                mUnscaledDrawingCache = bitmap;
            }
            if (opaque && use32BitCache) bitmap.setHasAlpha(false);
        } catch (OutOfMemoryError e) {
            // If there is not enough memory to create the bitmap cache, just
            // ignore the issue as bitmap caches are not required to draw the
            // view hierarchy
            if (autoScale) {
                mDrawingCache = null;
            } else {
                mUnscaledDrawingCache = null;
            }
            mCachingFailed = true;
            return;
        }

        clear = drawingCacheBackgroundColor != 0;
    }

    Canvas canvas;
    if (attachInfo != null) {
        canvas = attachInfo.mCanvas;
        if (canvas == null) {
            canvas = new Canvas();
        }
        canvas.setBitmap(bitmap);
        // Temporarily clobber the cached Canvas in case one of our children
        // is also using a drawing cache. Without this, the children would
        // steal the canvas by attaching their own bitmap to it and bad, bad
        // thing would happen (invisible views, corrupted drawings, etc.)
        attachInfo.mCanvas = null;
    } else {
        // This case should hopefully never or seldom happen
        canvas = new Canvas(bitmap);
    }

    if (clear) {
        bitmap.eraseColor(drawingCacheBackgroundColor);
    }

    computeScroll();
    final int restoreCount = canvas.save();

    if (autoScale && scalingRequired) {
        final float scale = attachInfo.mApplicationScale;
        canvas.scale(scale, scale);
    }

    canvas.translate(-mScrollX, -mScrollY);

    mPrivateFlags |= PFLAG_DRAWN;
    if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
            mLayerType != LAYER_TYPE_NONE) {
        mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
    }

    // Fast path for layouts with no backgrounds
    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        dispatchDraw(canvas);
        drawAutofilledHighlight(canvas);
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().draw(canvas);
        }
    } else {
        draw(canvas);
    }

    canvas.restoreToCount(restoreCount);
    canvas.setBitmap(null);

    if (attachInfo != null) {
        // Restore the cached Canvas for our siblings
        attachInfo.mCanvas = canvas;
    }
}

最后调用dispatchDraw(canvas);把绘制分发到子view。

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

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
    /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
     *
     * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
     * HW accelerated, it can't handle drawing RenderNodes.
     */
    boolean drawingWithRenderNode = mAttachInfo != null
            && mAttachInfo.mHardwareAccelerated
            && hardwareAcceleratedCanvas;
    ...

    if (hardwareAcceleratedCanvas) {
        // Clear INVALIDATED flag to allow invalidation to occur during rendering, but
        // retain the flag's value temporarily in the mRecreateDisplayList flag
        mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
        mPrivateFlags &= ~PFLAG_INVALIDATED;
    }

    RenderNode renderNode = null;
    Bitmap cache = null;
    int layerType = getLayerType(); // TODO: signify cache state with just 'cache' local

//soft绘制,如果子view并不是LAYER_TYPE_SOFTWARE,但drawingWithRenderNode为false,所以也会进入软件绘制流程。
    if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
//当然也有可能子view的layerType为LAYER_TYPE_NONE,那么就无法创建drawing cache,
//反而如果子view的layerType为LAYER_TYPE_HARDWARE,那么也会调用buildDrawingCache创建。
         if (layerType != LAYER_TYPE_NONE) {
             // If not drawing with RenderNode, treat HW layers as SW
             layerType = LAYER_TYPE_SOFTWARE;
             buildDrawingCache(true);
        }
        cache = getDrawingCache(true);
    }

//硬件绘制
    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 (!renderNode.isValid()) {
            // Uncommon, but possible. If a view is removed from the hierarchy during the call
            // to getDisplayList(), the display list will be marked invalid and we should not
            // try to use it again.
            renderNode = null;
            drawingWithRenderNode = false;
        }
    }

    int sx = 0;
    int sy = 0;
    if (!drawingWithRenderNode) {
        computeScroll();
        sx = mScrollX;
        sy = mScrollY;
    }

    final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
    final boolean offsetForScroll = cache == null && !drawingWithRenderNode;

    ...

    if (!drawingWithDrawingCache) {
        if (drawingWithRenderNode) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
        } else {
//如果子view为LAYER_TYPE_NONE那么就不会创建drawing cache,那么就需要执行绘制代码
            // Fast path for layouts with no backgrounds
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                dispatchDraw(canvas);
            } else {
                draw(canvas);
            }
        }
    } else if (cache != null) {
//soft layer进入此逻辑
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
            // no layer paint, use temporary paint to draw bitmap
            Paint cachePaint = parent.mCachePaint;
            if (cachePaint == null) {
                cachePaint = new Paint();
                cachePaint.setDither(false);
                parent.mCachePaint = cachePaint;
            }
            cachePaint.setAlpha((int) (alpha * 255));
            canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
        } else {
            // use layer paint to draw the bitmap, merging the two alphas, but also restore
            int layerPaintAlpha = mLayerPaint.getAlpha();
            if (alpha < 1) {
                mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
            }
            canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
            if (alpha < 1) {
                mLayerPaint.setAlpha(layerPaintAlpha);
            }
        }
    }

    ...

    mRecreateDisplayList = false;

    return more;
}

如果子view并不是LAYER_TYPE_SOFTWARE,但根据判断drawingWithRenderNode为false,所以也会进入软件绘制流程,当然还需要根据子view的layerType来决定是否调用buildDrawingCache。

所以说如果父view是soft layer,那么他的子view的layerType为hardware也会用软件绘制,如果子view是none就直接调用draw代码(不管是否drawing cache有效)。

public void buildDrawingCache(boolean autoScale) {
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ? mDrawingCache == null : mUnscaledDrawingCache == null)) {
        try {
            buildDrawingCacheImpl(autoScale);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
}

可以看到如果target的子view的cache有效的话就直接返回,不重建。然后在draw(Canvas canvas, ViewGroup parent, long drawingTime)最后把子view的cache绘制到target的canvas上。

 

父view的父view是soft layer,子view调用了invalidate/setTranslationX

当前view树的部分层级结构。

soft layer

--hardware layer

----none

 

  • none的 view调用了invalidate方法,

mPrivateFlags |= PFLAG_DIRTY;

mPrivateFlags |= PFLAG_INVALIDATED;

mPrivateFlags &= ~PFLAG_DRAWN;

mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

 

  • none的 view调用了setTranslationX方法

mPrivateFlags 不变

 

  • hardware layer的 view

mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;

mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

 

  • soft layer的 view

mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;

mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

target = this;

 

 

由于父view是soft layer,所以子view的绘制都是用soft绘制,有没有cache取决于该子view是否设置了layer(不管hardware layer,还是software layer)。

之后在绘制hardware layer的view时,会去调用buildDrawingCache(boolean autoScale),由于没有PFLAG_DRAWING_CACHE_VALID,所以就直接进入buildDrawingCacheImpl,

 

由此可以得出结论,子view调用了invalidate,那么soft layer、hardware layer、none都需要重建drawing cache。

子view调用了setTranslationX,那么soft layer、hardware layer都需要重建drawing cache,none的 view复用cache。

 

 

 

 

 

 

posted @ 2019-10-28 03:23  嘤嘤嘤123  阅读(2042)  评论(0编辑  收藏  举报