invalidate原理


在Android视图绘制开发中,invalidate方法常用,在主线程中调用它,用于触发视图的绘制刷新。下面我们分析一下该方法的主要流程,看一下一个视图执行该方法后是如何进行刷新的,参考源码。场景,添加一个简单的正方形视图,点击触发它的invalidate方法,场景比较简单,代码就补贴了,从View#invalidate方法开始分析。

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

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

调用View#invalidateInternal方法,绘制区域是视图相对自己坐标系的坐标值(0,0,width,height)。

mTop:视图相对父容器坐标系的上侧边界坐标。
mLeft:视图相对父容器坐标系的左侧边界坐标。
mRight-mLeft:视图宽度。
mBottom-mTop:视图高度。

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
                        boolean fullInvalidate) {
    ...
    //跳过绘制,如非VISIBLE时
    if (skipInvalidate()) {
        return;
    }
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) ==....) {
        ...
        if (invalidateCache) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }
        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);
            //告诉父View绘制的区域
            p.invalidateChild(this, damage);
        }
        ...
    }
}

绘制视图增加PFLAG_INVALIDATED标志,删除PFLAG_DRAWING_CACHE_VALID标志。父视图ViewParent ,调用它的#invalidateChild方法,传入绘制区域Rect,该区域是相对视图坐标系的值,即(0,0,width,height)。
ViewGroup实现ViewParent接口,ViewGroup#invalidateChild方法。参数告诉父视图,绘制的子视图以及子视图的坐标区域(相对子视图自己的坐标系)。

public final void invalidateChild(View child, final Rect dirty) {
    ViewParent parent = this;
    final AttachInfo attachInfo = mAttachInfo;
    //AttachInfo不能是空
    if (attachInfo != null) {
        //如果子View正在动画
        final boolean drawAnimation = (child.mPrivateFlags & 
                    PFLAG_DRAW_ANIMATION) == PFLAG_DRAW_ANIMATION;
        //View是否存在矩阵变换
        Matrix childMatrix = child.getMatrix();
        //完全不透明,没有动画
        final boolean isOpaque = child.isOpaque() && !drawAnimation &&
                child.getAnimation() == null && childMatrix.isIdentity();
        //子View设置标志dirty
        int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;
        //LayerType不是LAYER_TYPE_NONE
        if (child.mLayerType != LAYER_TYPE_NONE) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        final int[] location = attachInfo.mInvalidateChildLocation;
        location[CHILD_LEFT_INDEX] = child.mLeft;
        location[CHILD_TOP_INDEX] = child.mTop;
        .....
        do {
            View view = null;
            if (parent instanceof View) {
                view = (View) parent;
            }
            ...
            if (view != null) {
                if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
                        view.getSolidColor() == 0) {
                    opaqueFlag = PFLAG_DIRTY;
                }
                ...
            }
            parent = parent.invalidateChildInParent(location, dirty);
            if (view != null) {
                // 矩阵变换相关
                ...
                Matrix m = view.getMatrix();
                if (!m.isIdentity()) {
                    RectF boundingRect = attachInfo.mTmpTransformRect;
                    ...
                    dirty.set(...);
                }
            }
        } while (parent != null);
    }
}

首先,该方法有一个向上层查找父视图的遍历,只要父视图存在,一直调用父视图的invalidateChildInParent方法,到顶层视图DecorView,DecorView内部的ViewParent是ViewRootImpl,因此,ViewRootImpl#invalidateChildInParent方法。
invalidateChildInParent方法有两个参数,dirty区域是子视图区域(0,0,width,height),即invalidate方法的视图区域。location数组存储子视图相对父视图坐标系的左上坐标值,即mLeft和mTop。

public ViewParent invalidateChildInParent(final int[] location, 
                    final Rect dirty) {
    if ((mPrivateFlags & PFLAG_DRAWN) == ....) {
        if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | ...) {
            //变换成相对父视图的坐标系的值。
            dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                            location[CHILD_TOP_INDEX] - mScrollY);
            if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
                 //与父View坐标系联合,dirty可能变为父亲视图的区域。
                 dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
            }
            //获取父View相对它的父节点的左侧和上侧距离
            final int left = mLeft;
            final int top = mTop;

            if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                //有此标志,dirty区域已经是相对父视图的坐标系的值了,
                //与父视图相交,区域有交集。在没有Scroll的情况下。
                //其实就是视图自己的区域(相对父视图坐标系)
                if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
                    dirty.setEmpty();
                }
            }
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            //location设置相对父视图的位置。
            location[CHILD_LEFT_INDEX] = left;
            location[CHILD_TOP_INDEX] = top;
            ...
            //返回它的父视图。
            return mParent;
        } else {
            .....
            location[CHILD_LEFT_INDEX] = mLeft;
            location[CHILD_TOP_INDEX] = mTop;
            if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
            } else {
                dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
            }
            ....
            return mParent;
        }
    }
    return null;
}

首先,入参dirty区域的offset方法,加上location数组偏移,新区域是子视图相对于父视图坐标系的值,并且减去此时父视图mScrollX/mScrollY偏移,比如,父视图有一个向上的ScrollY偏移值。则整个子视图相对父节点的mBottom与mTop都会减小ScrollY。
然后,重新设置location数组元素值,设置成父视图相对它的父视图的左上侧距离。
若不考虑dirty的其他变化,union或interset,一层层将新location与dirty传给父视图,最后的dirty是执行invalidate方法的绘制视相对于顶层视图坐标系的坐标位置区域。
当设置FLAG_CLIP_CHILDREN标志时,仅绘制调用invalidate方法的视图区域,不绘制其父视图的其他区域,这是默认设置。若不设置此标志,dirty区域将union联合父视图区域,变成父视图区域。
每一个父视图删除PFLAG_DRAWING_CACHE_VALID标志。
最后,触发ViewRootImp#invalidateChildInParent方法。
绘制区域图。

invalidate原理_第1张图片
invalidate绘制区域.png

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
   //保证是创建视图树结构的线程
   checkThread();
   if (dirty == null) {
       invalidate();
       return null;
   } else if (dirty.isEmpty() && !mIsAnimating) {
       //如果dirty内容为空,什么都不做,返回。
       return null;
   }

   if (mCurScrollY != 0 || mTranslator != null) {
       mTempRect.set(dirty);
       dirty = mTempRect;
       if (mCurScrollY != 0) {
           dirty.offset(0, -mCurScrollY);
       }
       if (mTranslator != null) {
           mTranslator.translateRectInAppWindowToScreen(dirty);
       }
       if (mAttachInfo.mScalingRequired) {
           dirty.inset(-1, -1);
       }
   }

   invalidateRectOnScreen(dirty);

   return null;
}

最后的入参,dirty区域,调用invalidate的视图,相对顶层视图坐标系的坐标值。
根据mCurScrollY设置dirty区域偏移,真正绘制在ViewRootImpl#invalidateRectOnScreen方法。绘制屏幕上的一块待更新脏区域。

private void invalidateRectOnScreen(Rect dirty) {
    final Rect localDirty = mDirty;
    .....
    localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
    final float appScale = mAttachInfo.mApplicationScale;
    final boolean intersected = localDirty.intersect(0, 0,
                (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    if (!intersected) {
        localDirty.setEmpty();
    }
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
        scheduleTraversals();
    }
}

mDirty是保存在ViewRootImpl的一块脏区域。这里得到的mDirty区域是空的,与dirty区域union联合后,mDirty值变成dirty区域。再与窗体区域intersect相交,查看该区域是否在窗体中。
触发一次scheduleTraversals方法,此时,mDirty区域已经变为视图待更新区域了

最终的绘制区域dirty是执行invalidate方法当视图相对整个窗体坐标系的Rect区域。

调试结果图。

invalidate原理_第2张图片
调试结果图.png

手机模拟器绘制图。
invalidate原理_第3张图片
手机模拟器绘制图.png
黄色区域为绘制区域视图。
和invalidate一样,requestLayout方法也会触发 scheduleTraversals方法,但是requestLayout会设置一个mLayoutRequested标志,该标志会触发测量和布局,如果我们单纯的仅仅是视图刷新,不涉及大小的改变,只用invalidate刷新就可以了。

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

遍历后,调用ViewRootImpl#performDraw方法,draw(fullRedrawNeeded)方法,fullRedrawNeeded是false,这里仅仅绘制一个区域。
硬件渲染时,不需要提供dirty区域,将dirty置空,软件渲染才需要dirty区域。

private void draw(boolean fullRedrawNeeded) {
    Surface surface = mSurface;
    ...
    if (fullRedrawNeeded) {
        mAttachInfo.mIgnoreDirtyState = true;
        dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    }
    ...
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
            ...
            dirty.setEmpty();//设置空。
            mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
        } else {
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        }
    }
}

在硬件渲染时,只重绘触发invalidate方法的视图,具体流程图。
invalidate原理_第4张图片
invalidate方法时硬件渲染流程.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();
    view.mRecreateDisplayList = false;
}

从顶层视图开始,由invalidate方法引起的一次渲染视图,整个视图树结构已经建立好了。此时,顶层视图DecorView没有设置PFLAG_INVALIDATED标志,不存在mRecreateDisplayList重建标志,不需要重建DisplayList。
进入DecorView#updateDisplayListIfDirty方法

public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
    if (!canHaveDisplayList()) {
        return renderNode;
    }
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
                || !renderNode.isValid()
                || (mRecreateDisplayList)) {
        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;
}

RenderNode的isValid标志有效,并且没有mRecreateDisplayList标志,因此,不会重建顶层视图的Canvas数据。无PFLAG_DRAWING_CACHE_VALID标志(在前面已经将所有父视图该标志删除)。
进入dispatchGetDisplayList方法,将绘制事件派发给其他的视图节点,该方法在View是空方法,ViewGroup中重写。

protected void dispatchGetDisplayList() {
    final int count = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < count; i++) {
        final View child = children[i];
        if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || 
                            child.getAnimation() != null)) {
            //判断子视图是否需要重建DisplayList
            recreateChildDisplayList(child);
        }
    }
    ...
}

遍历每个子视图,recreateChildDisplayList方法,根据具体情况重建视图的绘制数据。

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

子视图无PFLAG_INVALIDATED标志时,不设置mRecreateDisplayList标志,因此,在子视图updateDisplayListIfDirty方法不会重建DisplayList绘制数据,这时,和顶层视图一样,直接返回当前视图RenderNode,并继续将绘制事件分发给下层视图。
当到达invalidate的视图时,PFLAG_INVALIDATED绘制标志存在,将设置mRecreateDisplayList标志,在该视图的updateDisplayListIfDirty方法中重建它的DisplayList绘制数据,调用View#onDraw方法。

总结

硬件渲染,最终仅将invalidate视图的绘制操作数据写入gpu重绘。invalidate基本流程图。
invalidate原理_第5张图片
invalidate基本流程图.jpg

任重而道远

你可能感兴趣的:(invalidate原理)