Invalidate简单分析

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()未传参调用时默认为true
       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.
       //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 time
        int 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 parent
                Matrix 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 container
                dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
            }

            if (mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
            }

            return mParent;
        }
    }

    return null;
}

invalidateChildInParent 方法计算子节点是否超出了父节点的剪裁空间,如果超出了,则将dirty置空。

最后调到 ViewRootImplinvalidateChildInParent

@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 region
    final 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 theirs
        if (renderNode.hasDisplayList()
                && !mRecreateDisplayList) {
            // ......
            dispatchGetDisplayList();
            // ......

            return renderNode; // no work needed
        }
		
        // 更新自己的DisplayList
        // ...
    }
}

总结

调用 invalidate()方法之后,会一直往上走标脏的同时设置脏区域。相较于重新全屏绘制来讲,在于会做剪裁区域的优化以及其他子树Display缓存重复使用的优化。和 Flutter 的分层渲染相比,优化还是有差距的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值