View中requestLayout和Invalidate源码分析

Posted by Chejdj Blog on April 2, 2018

我们都知道requestLayout和Invalidate都会导致View的绘制,那他们到底,有什么区别呢?我们可以去View的源码看看究竟(因为没有看过相关源码,这里重新补一下,自己太菜了)

requestLayout的源码分析

首先我贴出,requestlayout的注释

1
2
3
4
5
6
7
8
9
 * Call this when something has changed which has invalidated the
 * layout of this view. This will schedule a layout pass of the view
 * tree. This should not be called while the view hierarchy is currently in a layout
 * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
 * end of the current layout pass (and then layout will run again) or after the current
 * frame is drawn and the next layout occurs.
 *
 * <p>Subclasses which override this method should call the superclass method to
 * handle possible request-during-layout errors correctly  

当View发生改变使得这个view的布局无效的时候,调用该方法,如果View正在请求布局的时候,View树正在进行布局,那么requestlayout会等到布局流程完成之后,或则绘制流程完成且下一次布局出现的时候执行。
下面看源码
if (mMeasureCache != null) mMeasureCache.clear();
mMeasureCache是属于LongSparseLongArray类型的,可以看到这是一种比使用HasMap更加高效来存储longs类型的容器

  • SparseArray mapping longs to Objects. Unlike a normal array of Objects,
  • there can be gaps in the indices. It is intended to be more memory efficient
  • than using a HashMap to map Longs to Objects, both because it avoids
  • auto-boxing keys and its data structure doesn’t rely on an extra entry object
  • for each mapping.
1
2
3
4
5
6
7
8
9
10
11
12
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

第一行中mAttachInfo是AttachInfo类型,它表示依附于父View窗口给子View的一些信息,一般都不为空

  • A set of information given to a view when it is attached to its parent window.

mAttachInfo.mViewRequestinglayout用于记录哪一个View发起了requestlayout信息,整个View源码就下面的mAttachInfo.mViewRequestingLayout=this赋值,默认开始为null

Used to track which View originated a requestLayout() call, used when requestLayout() is called during layout.

if(viewRoot!=null && viewRoot.isInLayout())判断当前这个View树是否在进行布局流程,如果在的话就调用requestLayoutoutDuringLayout(this)让这一次的布局延时进行。

1
2
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;

设置mPrivateFlags的两个标志位,PFLAG_FORCE_LAYOUT,通过表面的意思可以知道这是一个布局标志位,就会执行View的mearsure()和layout()方法

1
2
3
4
5
6
if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }

最后一个判断就是把之前的标记清除一下,最重要的是前面的一个判断,如果父容器不为空,并且父容器没有LayoutRequest就调用父容器的requestLayout,因为父容器是ViewGroup没有重写requestLayout,但是它的父类也是View就又会调用它父容器的requestLayout,不断上传并且为父容器设置PFLAG_FORCE_LAYOUTPFLAG_INVALIDATED两个标志位,最后到顶级容器DecorView,但是DecorView的mparent是ViewRootImpl对象,它也设置两个标志位,然后就调用ViewRootImpl的requestLayout

1
2
3
4
5
6
7
public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

mHandlingLayoutInLayoutrequest是一个boolean类型,它会在performLayout中被设置为true,这里表示的意思就是当前并不处于Layout过程中

1
2
3
4
5
6
7
8
9
10
11
12
13
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
}

这里面的成员mChoregrapher是属于Choreographer类型,大致意思这个按照一帧一帧绘制。该方法对于绘制的请求经过了Choreographer的编排后,最终会调用回ViewRootImpl.doTraversal()方法,在doTraversal又会调用performTraversals()方法,这个代码太长了,相信知道View的工作原理的知道,performTraversals是View绘制流程的开始,它会经过measuer,layout,draw三个过程。所以看到这里,我们大概就知道了requestLayout会触发整个界面的绘制(measuer,layout,draw)(注意前面每一层设置的标志位很重要,因为它决定了界面绘制是否需要执行)

The choreographer receives timing pulses (such as vertical synchronization)

  • from the display subsystem then schedules work to occur as part of rendering
  • the next display frame.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void layout(int l, int t, int r, int b) {
......
//判断标志位
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
....
}

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
 //是否设置了 PFLAG_FORCE_LAYOUT标志位
 final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
 //一般这里为false,在View不变的情况下
   if (forceLayout || needsLayout) {//判断是否需要measuer 
}
}


public void draw(Canvas canvas) {
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

//如果设置了 PFLAG_DIRTY_MASK, &的结果不相等,返回false,所以就绘制
// PFLAG_DIRTY_OPAQE标志位表示该View是否已经被invalidate
if (!dirtyOpaque) {  
   drawBackground(canvas);//后面还有判断就一一写了
}

}


总结一下调用requestlayout,会导致自己的View设置两个标志位,并且向上调用父类的requestlayout并且设置标志位,不断向上传递,最后上传到ViewRootImp执行performTraversals执行measure,layout(在三大流程中检查标志位)(更新一下,不一定执行onDraw,没有设置PFLAG_DIRTY_MASK标志位)

Invalidate源码分析

我们来看一下View的invalidate调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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 (skipInvalidate()) {//判断该View是否可见 或则是否在动画中
            return;
}
//判断View的一些标志位,判断View是否需要重新绘制
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)) {
... //里面执行绘制方法
      mPrivateFlags |= PFLAG_DIRTY; //给这个View设置PFLAG_DIRTY标志位
}
...
// 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);//l,t,r,b是需要重新绘制的View的范围
                p.invalidateChild(this, damage);//p是ViewParent类型,向父类传递绘制事件
}
}
....


public final void invalidateChild(View child, final Rect dirty) { //dirty是需要绘制的区域

....//内部存在一个循环

do {
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                }

                if (drawAnimation) {
                    if (view != null) {
                        view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                    } else if (parent instanceof ViewRootImpl) {
                        ((ViewRootImpl) parent).mIsAnimating = true;
                    }
                }

                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
                // flag coming from the child that initiated the invalidate
                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;
                    }
                }
                //循环调用parent.parent的invalidateChildInparent方法,向上传递
               //最终还是会传递到RootViewImp类中
                parent = parent.invalidateChildInParent(location, 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);
}
...

public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    ....
   dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                        location[CHILD_TOP_INDEX] - mScrollY);
    ....
   dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
    ....   //这个方法就是对于dirty区域做一些转换,变成父容器的dirty区域
}

接下来我们看看ViewRootImp的invalidateChildInParent方法,它会触发scheduleTraversals();方法,然后执行onMeasure,onlayout,draw方法,但是它并没有设置PFLAG_FORCE_LAYOUT标志位,所以不执行,只执行draw方法。
总结一波调用Invalidate方法给该View设置PFLAG_DIRTY标志位,然后不断计算在父容器中的区域向上传递(设置标志位),最终传递到RootViewImp执行scheduleTraversals然后执行performTraversals,然后measure,layout,draw三个方法,由于设置的标志位,只能执行draw方法,前面两个不执行(不是不执行,只是不执行特有的方法)