美文网首页Android
invalidate原理

invalidate原理

作者: gczxbb | 来源:发表于2018-04-21 11:36 被阅读812次

    在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绘制区域.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区域。

    调试结果图。

    调试结果图.png
    手机模拟器绘制图。 手机模拟器绘制图.png 黄色区域为绘制区域视图。
    遍历后,调用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方法时硬件渲染流程.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基本流程图.jpg

    任重而道远

    相关文章

      网友评论

        本文标题:invalidate原理

        本文链接:https://www.haomeiwen.com/subject/cjbgkftx.html