View测量流程深度分析

  在看View源码的时候,有一个算数的知识我们一定要掌握,就是& | ^ 这三个运算符的叠加使用。

  • a&~b: 清除标志位b
  • a|b :增加标志位b
  • a&b:取出标志位b
  • a^b:取出a与b的不同部分

setFlag(int flags,int mask)

在View的构造函数里面有两个变量:viewFlagValuesviewFlagMasks。他们通过|运算符把元素添加到里面,这些元素有:

  • fitsSystamWindow:viewFlagValues|=FITS_SYSTEM_WINDOWS
  • view_focusable:(viewFlagValues & ~FOCUSABLE_MASK)|getFoucusableAttribute(a)
  • …..

(还有很多比如可不可见,自动获取焦点,可不可点击都是通过|运算符添加到viewFlagValues里面的。具体可以看View的构造函数。)

  上面的属性全部设置完成后,在调用setFlags(viewFlagValues,viewFlagMasks)将值全部设置到mViewFlags里面。

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
  void setFlags(int flags, int mask) {
// mView
int old = mViewFlags;
// 看似一通计算,实则就是将flags的值赋值给了mViewFlags
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
// 接着通过^运算取出他们的不同部分。。
int changed = mViewFlags ^ old;
if (changed == 0) {
// 为0代表并没有变化
return;
}
// 这个flag就是传进来的flag标志
final int newVisibility = flags & VISIBILITY_MASK;
if (newVisibility == VISIBLE) {
// 这个判断的原理是 上面进行了^运算取出了他们的不同部分,如果之前是可见的,现在还是可见的,那么^结果后的changed & VISIBILITY_MASK的结果一定是0。这样做是为了防止设置了同样的可见属性导致无用的页面刷新。
if ((changed & VISIBILITY_MASK) != 0) {
mPrivateFlags |= PFLAG_DRAWN;
invalidate(true);
needGlobalAttributesUpdate(true);
shouldNotifyFocusableAvailable = hasSize();
}
}
//从这里可以看到当视图变成不可见的时候,会去主动触发一个requestLayout
if ((changed & GONE) != 0) {
needGlobalAttributesUpdate(false);
requestLayout();

if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
if (hasFocus()) {
clearFocus();
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).clearFocusedInCluster();
}
}
clearAccessibilityFocus();
destroyDrawingCache();
if (mParent instanceof View) {
// GONE views noop invalidation, so invalidate the parent
((View) mParent).invalidate(true);
}
mPrivateFlags |= PFLAG_DRAWN;
}
if (mAttachInfo != null) {
mAttachInfo.mViewVisibilityChanged = true;
}
}
// 在View的构造函数里面触发setFlags不会触发这个里面的方法
if ((changed & DRAW_MASK) != 0) {
requestLayout();
invalidate(true);
}
}

通过对setFlags()方法分析他的里面有两个地方会主动触发requestLayout:

  1. 当页面设置成了GONE
  2. 当changed标志里面加上了DRAW_MASK

View里面的参数mAttachInfo是什么时候设置上值得:

     在dispatchAttachedToWindow(AttachInfo info,int visibility)。

1
2
3
 void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
}

     在dispatchDetachedFromWindow里面将mAttacInfo设置为了null。

现在问题来了,这个dispatchAttachedToWindow()方法是什么时候被调用的。

  在ViewRootImpl里面主动触发requestLayout得时候会去一步步走到ViewRootImpl.performTraversals()方法。在performTraversals方法里面调用了host.dispatchAttachedToWindow方法这个host也就是DevorViewDecorview是一个ViewGroup,所以现在进到ViewGroup得dispatchAttachedToWindow方法:

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
// ViewGroup中的实现:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
// 先调用自己的
super.dispatchAttachedToWindow(info, visibility);
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
// 递归调用每个child的dispatchAttachedToWindow方法
// 典型的深度优先遍历
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info,
combineVisibility(visibility, view.getVisibility()));
}
}

// View中的实现:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
//System.out.println("Attached! " + this);
mAttachInfo = info;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
if (mFloatingTreeObserver != null) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();

ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}

int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(vis);
}

// Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
// As all views in the subtree will already receive dispatchAttachedToWindow
// traversing the subtree again here is not desired.
onVisibilityChanged(this, visibility);

if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
// If nobody has evaluated the drawable state yet, then do it now.
refreshDrawableState();
}
needGlobalAttributesUpdate(false);
}

  可以看到执行了一个循环,遍历了全部的子view的dispatchAttachedToWindow方法。

  所以view的dispatchAttachedToWindow是在onResume阶段,最早由ViewRootImpl触发,然后交给了ViewGroup。遍历调用了所有的子view的dispatchedToWindow。

   在ViewRootImpl的构造函数里面会创建对象mAttachInfo:View.AttachInfo,在AttachInfo的构造函数里面,有一个Handler的参数,他是在ViewRootImpl类里面就已经创建好了ViewRootHandler。这个mAttachInfo也是一层层的传递到ViewRootImpl里面的所有子view里面。那他是通过什么传递的呢,就是通过

dispatchAttachedToWindow方法传递下去的。

  既然现在分析到了这里,那我就看下子view的dispatchAttachedToWindow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;

// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();

ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}
}

    在view的dispatchAttachedToWindow方法里面,可以看到有这么一行代码:mRunQueue.executeActions(info.mHandler)>。通过名字execute意思是执行的意思,他想表达的意思就是执行mRunQueue里面的全部Message事件。

    那我们就来看下这个mRunQueue:HandlerActionQueue的数据结构:

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
public class HandlerActionQueue {
private HandlerAction[] mActions;//报错所有的待执行的message
private int mCount;
// 添加一个message
public void post(Runnable action) {
postDelayed(action, 0);
}

public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
// 删除一个message
public void removeCallbacks(Runnable action) {
synchronized (this) {
final int count = mCount;
int j = 0;

final HandlerAction[] actions = mActions;
for (int i = 0; i < count; i++) {
if (actions[i].matches(action)) {
continue;
}

if (j != i) {
actions[j] = actions[i];
}

j++;
}
mCount = j;
for (; j < count; j++) {
actions[j] = null;
}
}
}
// 执行一个message。通过看源码,这个executeActions是在View的dispatchAttachedToWindow方法
//里面被主动调用了。dispatchAttachedToWindow方法又是在onResume里面被ViewRootImpl触发的。
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}

mActions = null;
mCount = 0;
}
}

public int size() {
return mCount;
}

public Runnable getRunnable(int index) {
if (index >= mCount) {
throw new IndexOutOfBoundsException();
}
return mActions[index].action;
}

public long getDelay(int index) {
if (index >= mCount) {
throw new IndexOutOfBoundsException();
}
return mActions[index].delay;
}

private static class HandlerAction {
final Runnable action;
final long delay;

public HandlerAction(Runnable action, long delay) {
this.action = action;
this.delay = delay;
}

public boolean matches(Runnable otherAction) {
return otherAction == null && action == null
|| action != null && action.equals(otherAction);
}
}
}

  通过这次的代码分析可以知道,我们为什么在onCreate里面调用了view.post方法,而不是在onResume里面了。因为在onCreate里面调用view.post就会往view的mRunQueue数组里面我们的Message消息。然后在页面走到onResume的生命周期的时候,由ViewRootImpl触发了dispatchAttachedToWindow方法层层调用到子view的dispatchAttachedToWindow方法,然后触发了mRunQueue.executeAcions()方法,执行我们在onCreate里面通过view.post添加的所有Message消息

    继续看回我们的performTraversals()

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
//ViewRootImpl.java
private void performTraversals() {
...
// 执行ViewRootImpl里面添加的所有message。目前我在源码看到ViewRootImpl只会在
//ViewRootImpl.performLayout方法里面会添加一条message。这个message的内容就是调用子view的
//requestLayout方法:
getRunQueue().executeActions(mAttachInfo.mHandler);
performMeasure()
...
}

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
// 唯一在ViewRootImpl里面添加的message
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
view.requestLayout();
}
}
});
}
}
}

     继续看ViewRootImpl.performTraversals()可以看到perFormMeasure方法:

1
2
3
4
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

  继续看下mView.measure(),分析这个代码可以发现**:**

1. 如果页面需要强制重新布局,那么不管页面的长宽有没有改变都不会调用onMeasure方法.

2. 如果强制更新布局为false,但是needsLayout是true.然后如果长宽没有改变,那么不会执行onMeasure方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 如果是强制布局或者需要重新布局 那么就进入
if (forceLayout || needsLayout) {
// 在Activity创建完成第一次走到这里面的时候mMesureCache这个数组里面是没数据的,所以
// 这个判断条件一定是符合要求的.回进入调用onMeasure方法.
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
}

mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

从这个代码可以

invalidate

核心思想:对于下面这个View树而言,如果A视图没有阿尔法通道,即完全不透明,那么理论上讲,如果只是A视图需要重绘,则不会导致A视图所在的父视图也进行重绘,只有当A视图隐藏时,才会通知父视图B绘制之前A所占领的区域即可。然而,对于包含阿尔法通道的视图系统而言,屏幕的效果是由A和B及底层所有父视图共同产生的,因此,每次当A视图需要重新绘制的时候,其底层的父视图B及所有父视图都需要进行重绘该区域。所以当A需要重新绘制的时候,必须通知父视图也绘制该区域。

img4

invalidate的意思就是使失效:

  1. 使什么失效?使当前的状态失效。

  2. 为什么要失效?因为只有标记为失效,系统才会在下一次屏幕刷新的时候执行重绘

  3. 什么时候失效?在例如点击效果,背景颜色,高度,长度等等当前屏幕元素需要更新的时候。

  4. 那么失效做了什么?为什么等下一次刷新呢?而不是直接出发重新绘制呢?这样做啥好处?

    invalidate三部曲之始于invalidate

  invalidate的调用原理

img1

  View.invalidateInternal

img1

打上DIRTY标记
  为后面的Draw做准备。

mPrivateFlags |= PFLAG_DIRTY;

调用父组件的invalidateChild
并将自身的left,top,right,bottom通知给父组件

ViewGroup.invalidateChild

  循环执行父组件的invalidateChildInPatent,直到执行到ViewRootImpl完成后返回null。也就是尽头了。

img1

ViewGroup.invalidateChildParent

img1

ViewRootImpl.invalidateRectOnScreen

img1

加入重绘区域合集

localDirty.union(dirty.left,dirty.top,dirty.right,dirty.bottom)

本次invalidate的dirty区域同之前的dirty区域合并。

1
2
3
4
5
 final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}

判断要重绘的区域是不是在屏幕内,也就是判断dirty和屏幕区域是否有交叉,屏幕外的就不用绘制了。

scheduleTraversals()

开始遍历绘制。

invalidate()使用场景

  1. 直接调用invalidate方法,请求重新draw()。但只会绘制调用者本身。
  2. setSelection()方法:请求重新draw,但只会绘制调用者本身
  3. setVisibilify():当View可视状态在INVISIBLE转换到VISIBILE时候,会间接调用invalidate()继而绘制该view
  4. setEnabled():请求重新draw(),但不会重新绘制任何视图包括视图调用者本身。

 

测量Measture过程

首先在ViewRootImpl里面触发了performTraversals()方法,在performTraversals()方法里面触发了performMeasure()方法。

1
2
3
4
5
6
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

可以看到调用了mView的measure方法,在mVIew的measure方法里面调用了onMeasure方法。这个mView我们都知道是DecorView,然后DecorView的父类是Framelayout。进入DecorView的onMeasure方法,首先调用了父类的onMeasure方法:

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
// FrameLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();

final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();

int maxHeight = 0;
int maxWidth = 0;
int childState = 0;

for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}

// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}

setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));

count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}

final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}

看下ViewGroup的measureChildWidthMargins方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

如果父亲是MeasureSpec.EXACTLY:

  • 如果孩子的测量值是大于等于0的,那么这个孩子的结果值就是设置的值,他的模式就是MeasureSpec.EXACTLY。

  • 如果孩子的测量值等于 MATCH_PARENT:那么这个孩子的结果值等于父宽度的最大值-padding-margin值,他的测量模式设置为MeasureSpec.EXACTLY。

  • 如果孩子的测量模式等于WRAP_CONTENT:那么这个孩子的结果值等于父宽度的最大值-padding-margin值,他的测量模式设置为MeasureSpec.AT_MOST。

如果父亲是MeasureSpecc.AT_MOST:

  • 如果孩子的测量值是确定值那么结果孩子的值就是他自己设置上去的值,测量模式设置为MeasureSpec.EXACTLY。
  • 如果孩子的测量值是MATCH_PARENT,那么结果值是父宽度-padding-margin。测量模式 = AT_MOST
  • 如果孩子的测量模式WRAP_CONTENT,那么结果值是父宽度-padding-magin。测量模式设置为AT_MOST

如果父亲设置的是UNSPECIFIED:

  • 如果孩子的测量值是确定值那么结果孩子的值就是他自己设置上去的值,测量模式设置为MeasureSpec.EXACTLY。
  • 如果孩子的测量值是MATCH_PARENT,那么结果值是父宽度-padding-margin。测量模式 = UNSPECIFIED
  • 如果孩子的测量值是WRAP_CONTENT,那么结果值是父宽度-padding-margin。测量模式 = UNSPECIFIED