invalivade 流程
背景
最近在做Flutter的分层渲染分析,发现Flutter的分层渲染可以让节点标脏限制在同一个 Layer 中,从而提升性能。然后想到 Android 在更新 DisplayList 的时候会判断节点 dirty.isEmpty,从而决定是否更新DisplayList,那么这个dirty是哪里来的呢,Andriod 原生是怎么判断一个节点是否需要更新的呢?
我们都知道要更新节点,需要调用 invalidate() 方法,所以需要分析一下这个方法。
分析
开头总结:invalidate会在 view 树中一级一级往上查找,一级一级的标脏。在查找的过程中会根据父节点的位置更新脏区域的偏移位置,如果遇到脏区域位于父节点剪裁区域的外面,或者其他导致 view 看不到的情况,那么就停止标脏。
invalidate
会设置缓存失效,调到 invalidateInternal
方法:
public void invalidate() {invalidate(true);
}void invalidate(boolean invalidateCache) {//mLeft、mRigth、mTop、mBottom记录的是当前View边界距离其父布局View边界的距离invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate) {// ...//如果当前视图为不可见状态且没有动画正在执行,且其父布局也没有过渡动画执行,则跳过if (skipInvalidate()) {return;}//当前View没有正在执行该方法//或绘制缓存可用或未重绘过或透明度发生改变//PFLAG_DRAWN会在该方法内去改标志位//PFLAG_INVALIDATED会在View.draw()方法执行时去掉该标志位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)) {//如果需要全部重绘,invalidate()未传参调用时默认为trueif (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.//damage记录的区域是需要更新的dirty区域,当前的坐标时相对于自身来设置的//通过不断调用到父类的invalidateChild()方法,来不断更新dirty区域的相对坐标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);}// ...}
}
invalidateInternal
方法主要是判断到底到底要不要重绘,判断条件包括是否可见 / 是否使用缓存 / 是否已经被设置了 invalidate。然后创建脏区域传给父节点的 invalidateChild
方法。 ViewParent
是个父类,ViewGroup
实现了它:
@Override
public final void invalidateChild(View child, final Rect dirty) {ViewParent parent = this;final AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) { //drawAnimation记录调用该方法的子View是否正在执行动画final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION)== PFLAG_DRAW_ANIMATION;//调用该方法的子View是否不透明:处于不透明状态且没有在执行动画且变化矩阵没有变化 //Matrix可以用于View的平移、缩放、扩放、旋转等操作,比如某些应用上的双指缩放功能Matrix childMatrix = child.getMatrix();final boolean isOpaque = child.isOpaque() && !drawAnimation &&child.getAnimation() == null && childMatrix.isIdentity();// Mark the child as dirty, using the appropriate flag// Make sure we do not set both flags at the same timeint opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;if (child.mLayerType != LAYER_TYPE_NONE) {mPrivateFlags |= PFLAG_INVALIDATED;mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;}//记录子View边界距离父View左边界和上边界的距离到Location中,用于下一段代码中的计算final int[] location = attachInfo.mInvalidateChildLocation; location[CHILD_LEFT_INDEX] = child.mLeft;location[CHILD_TOP_INDEX] = child.mTop; //如果子View设置了变换矩阵,则根据变换矩阵调整dirty区域if (!childMatrix.isIdentity() ||(mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {RectF boundingRect = attachInfo.mTmpTransformRect;boundingRect.set(dirty);Matrix transformMatrix;if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {Transformation t = attachInfo.mTmpTransformation;boolean transformed = getChildStaticTransformation(child, t);if (transformed) {transformMatrix = attachInfo.mTmpMatrix;transformMatrix.set(t.getMatrix());if (!childMatrix.isIdentity()) {transformMatrix.preConcat(childMatrix);}} else {transformMatrix = childMatrix;}} else {transformMatrix = childMatrix;}transformMatrix.mapRect(boundingRect);dirty.set((int) Math.floor(boundingRect.left),(int) Math.floor(boundingRect.top),(int) Math.ceil(boundingRect.right),(int) Math.ceil(boundingRect.bottom));}//这是一个从当前的布局View向上不断遍历当前布局View的父布局,最后遍历到ViewRootImpl的循环do {View view = null; //parent可能为ViewGroup类型,也可能为ViewRootImpl类型 //最后一次循环执行时为ViewRootImpl类型if (parent instanceof View) {view = (View) parent;}//如果子View正在执行动画,设置遍历的父布局View的动画标识if (drawAnimation) {if (view != null) {view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;} else if (parent instanceof ViewRootImpl) {((ViewRootImpl) parent).mIsAnimating = true;}}//设置当前ViewGroup的Dirty标识,表示当前的ViewGroup需要重绘if (view != null) {if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&view.getSolidColor() == 0) {opaqueFlag = PFLAG_DIRTY;}if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;}}//调用当前布局View的invalidateChildParent()方法,返回的值为当前布局View的父布局 //通过循环向上调用,最后返回的根布局是ViewRootImpl对象 parent = parent.invalidateChildInParent(location, dirty);// 更新 dirty 位置if (view != null) {// Account for transform on current parentMatrix m = view.getMatrix();if (!m.isIdentity()) {RectF boundingRect = attachInfo.mTmpTransformRect;boundingRect.set(dirty);m.mapRect(boundingRect);dirty.set((int) Math.floor(boundingRect.left),(int) Math.floor(boundingRect.top),(int) Math.ceil(boundingRect.right),(int) Math.ceil(boundingRect.bottom));}}} while (parent != null);}
}
invalidateChild
方法是关键,它的作用就是循环往上一直找到 ViewRootImpl
,在这个过程中一直更新 dirty的位置。接下来看 invalidateChildInParent
@Override
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) { //如果ViewGroup有没有动画执行或者动画已经完成if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=FLAG_OPTIMIZE_INVALIDATE) { //dirty记录的是最开始调到invalidate()的View的区域 //dirty的四个坐标值值在执行下面代码是相对于当前循环到上一个ViewGroup来确定的 //这里做了一个偏移动作,偏移的量是当前上一个ViewGroup相对于现在ViewGroup的偏移值 //做完下面的偏移操作后,dirty的四个坐标就是想对于当前ViewGroup的坐标值了dirty.offset([CHILD_LEFT_INDEX] - mScrollX,location[CHILD_TOP_INDEX] - mScrollY); //如果当前ViewGroup需要裁剪View //则将当前ViewGroup的区域与View的区域做求并集的操作if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {dirty.union(0, 0, mRight - mLeft, mBottom - mTop);}final int left = mLeft;final int top = mTop;//如果当前ViewGroup需要裁剪View,且ViewGroup区域与View区域没有并集,则dirty置空if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {dirty.setEmpty();}}mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;//用于循环到下一个ViewGroup时做offset操作location[CHILD_LEFT_INDEX] = left;location[CHILD_TOP_INDEX] = top;if (mLayerType != LAYER_TYPE_NONE) {mPrivateFlags |= PFLAG_INVALIDATED;}return mParent;} else {//如果当前ViewGroup中有动画要执行mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;location[CHILD_LEFT_INDEX] = mLeft;location[CHILD_TOP_INDEX] = mTop; //如果需要对子View裁剪则设置dirty为当前ViewGroup区域 //如果不需要则求当前ViewGroup区域与原ditry区域并集 if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {dirty.set(0, 0, mRight - mLeft, mBottom - mTop);} else {// in case the dirty rect extends outside the bounds of this containerdirty.union(0, 0, mRight - mLeft, mBottom - mTop);}if (mLayerType != LAYER_TYPE_NONE) {mPrivateFlags |= PFLAG_INVALIDATED;}return mParent;}}return null;
}
invalidateChildInParent
方法计算子节点是否超出了父节点的剪裁空间,如果超出了,则将dirty置空。
最后调到 ViewRootImpl
的invalidateChildInParent
:
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {checkThread();if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);//如果传入一个null drity,则表示要重绘当前ViewRootImpl指示的整个区域 //如果传入一个empty dirty,则表示经过计算需要重绘的区域不需要绘制 if (dirty == null) {invalidate();return null;} else if (dirty.isEmpty() && !mIsAnimating) {return null;}...invalidateRectOnScreen(dirty);return null;
}
再来一个绘制判断,然后调用invalidateRectOnScreen
:
private void invalidateRectOnScreen(Rect dirty) { //mDirty记录的是当前ViewRootImpl里还未进行重绘需要重绘的区域 //mDirty会在ViewRootImpl.draw()方法结尾处设置为empty final Rect localDirty = mDirty;if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {mAttachInfo.mSetIgnoreDirtyState = true;mAttachInfo.mIgnoreDirtyState = true;}// Add the new dirty rect to the current one //当前已有的dirty区域与此次dirty区域做并集localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);// Intersect with the bounds of the window to skip// updates that lie outside of the visible regionfinal float appScale = mAttachInfo.mApplicationScale; //处理窗口缩放与做完并集的localDirty做交集final boolean intersected = localDirty.intersect(0, 0,(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));//如果没有交集 if (!intersected) {localDirty.setEmpty();} //mWillDrawSoon在performTraversals()方法开始时置为true,结束时置false //如果没有在执行performTraversals &&(intersected || 正在执行动画)if (!mWillDrawSoon && (intersected || mIsAnimating)) {scheduleTraversals();}
}
在invalidateRectOnScreen
方法中与之前的脏区域做并集,与窗口缩放做交集后请求下一帧渲染。
这样一趟下来,只要脏区域可见,那么该节点和所有父节点都被标记了:mPrivateFlags |= PFLAG_INVALIDATED;
接下来在更新DisplayList
的时候 仅 mPrivateFlags
有 PFLAG_INVALIDATED
标记的更新:
// frameworks\base\core\java\android\view\ThreadedRenderer.java
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;
}// View.updateDisplayListIfDirty
public RenderNode updateDisplayListIfDirty() {final RenderNode renderNode = mRenderNode;// ......if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0|| !renderNode.hasDisplayList()|| (mRecreateDisplayList)) {// Don't need to recreate the display list, just need to tell our// children to restore/recreate theirsif (renderNode.hasDisplayList()&& !mRecreateDisplayList) {// ......dispatchGetDisplayList();// ......return renderNode; // no work needed}// 更新自己的DisplayList// ...}
}
总结
调用 invalidate()
方法之后,会一直往上走标脏的同时设置脏区域。相较于重新全屏绘制来讲,在于会做剪裁区域的优化以及其他子树Display缓存重复使用的优化。和 Flutter 的分层渲染相比,优化还是有差距的。