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。

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM