在前一篇博客Android O: View的绘制流程(一): 创建和加载中,
我们分析了系统创建和加载View的过程,这部分内容完成了View绘制的前置工作。
本文开始分析View的测量的流程。
一、绘制流程的起点
在分析View的测量的流程前,我们先来寻找一下界面绘制流程的起点。
当Activity启动时,会调用ActivityThread的handleLaunchActivity方法:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
............
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
.............
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
.............
} else {
.............
}
}
我们跟进一下handleResumeActivity函数:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
..........
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
.........
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
..........
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
...........
}
}
} else if (...) {
.......
}
.......
} else {
.........
}
}
- 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
从上述代码可以看出,解析完XML对应的View后,
最终将DecorView递交给WindowManager。
我们跟进一下WindowManagerImpl中的addView函数:
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
继续跟进WindowManagerGlobal中的代码:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
............
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
.............
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
..........
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...........
}
}
}
- 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
至此我们知道了,WindowManager将DecorView和对应的ViewRootImpl关联起来了。
现在来一起看看ViewRootImpl的setView函数:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...........
requestLayout();
...........
}
}
}
容易看出ViewRootImpl与View关联后,会调用requestLayout函数,
该函数将开启整个绘制流程。
眼见为实,我们来看看这个requestLayout函数:
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
...........
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...........
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
最后,我们来看看TraversalRunnable的实现:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
TraversalRunnable在执行时,会调用doTraversal函数,对应代码如下:
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
..........
performTraversals();
..........
}
}
绘制的主要逻辑定义于ViewRootImpl的performTraversals中,
该函数会遍历整个视图书,逐一绘制每个View。
performTraversals函数接近1000行左右且涉及较多琐碎的细节,
个人感觉没有逐行解析的必要,因此我们主仅关注主要的逻辑。
实际上performTraversals的代码流程可以大致分为三个阶段,如下所示:
private void performTraversals() {
.............
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
............
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
............
performLayout(lp, mWidth, mHeight);
............
performDraw();
}
总结一下上述整个代码的调用流程,大致如下所示:

二、Measure阶段
将performTraversals划分为三个阶段后,整体的逻辑就可以简化为下图:

现在我们来看看其中Measure阶段的代码。
2.1 MeasureSpec
在分析测量的代码前,我们先要了解一下MeasureSpec的概念。
MeasureSpec是定义于View.java中的内部类,表示一个32位的整形值。
它的高2位表示测量模式SpecMode,低30位表示在相应模式下的测量尺寸SpecSize。
目前SpecMode的取值可以为以下三种:
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
如注释所述:
UNSPECIFIED 表示不指定测量模式,对应的场景是:
父视图没有限制子试图大小,子试图可以是想要的任何尺寸。
这种模式基本用不到。
EXACTLY 表示精确测量模式,对应的场景是:
父视图已经指定了子试图的精确大小,此时测量值就是SpecSize的值。
当视图的layout_width或者layout_height指定为具体的数值,
或指定为match_parent时,该模式生效。
AT_MOST 表示最大值模式,对应的场景是:
当视图的layout_width或layout_height指定为wrap_content时,
子视图的尺寸可以是不超过父视图允许最大值的任何尺寸。
我们来看看前文代码中的getRootMeasureSpec函数:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
从代码来看,根据LayoutParams的参数,getRootMeasureSpec会得到对应模式的MeasureSpec。
其中主要用到的还是EXACTLY和AT_MOST模式。
对于DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;
对于普通的View,它的MeasureSpec由父视图的MeasureSpec和自身的LayoutParams共同决定。
2.2 measure
了解完MeasureSpec后,我们来看看performMeasure函数:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
........
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
........
}
我们跟进View的measure函数:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
............
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
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);
}
- 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
从上面的代码来看,当需要(包括强制)重新布局且不使用(包括无缓存)缓存数据时,
才会调用onMeasure进行View的测量工作。
上述代码的整体流程,大致如下图所示:

2.3 ViewGroup的onMeasure
onMeasure函数一般会被View的子类覆盖,因此对于DecorView而言,
实际调用的应该是FrameLayout的onMeasure方法。
我们来跟进一下FrameLayout的onMeasure方法:
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);
}
}
}
}
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
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++) {
............
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
- 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
从以上代码的执行流程,我们可以看到,作为容器的ViewGroup,
将通过measureChildWithMargins方法,对所有子View进行测量,
然后才会计算自身的测量结果。
FrameLayout的onMeasure函数整体流程可以概括为下图:

2.4 measureChildWithMargins
接下来,我们来看下ViewGroup的measureChildWithMargins方法:
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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
从上述代码可以看出,ViewGroup会利用getChildMeasureSpec函数计算出子View的约束条件,
然后再调用子View的measure函数。
我们看看getChildMeasureSpec函数:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
................
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
- 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
上面的方法展现了根据父View的MeasureSpec和子View的LayoutParams生成子View的MeasureSpec的过程,
从代码可以看出:
当子View指定了具体的大小时,resultSize就是指定的size,resultMode为EXACTLY,
父View对其没有影响;
当子View指定为MATCH_PARENT时,resultSize为父View可用的size,
resultMode与父View一致;
当子View指定为WRAP_CONTENT时,resultSize为父View可用的size,
resultMode为AT_MOST。
从前文我们知道,获取完子View的MeasureSpec后,
measureChildWithMargins就会调用子View的measure方法。
对于ViewGroup及其子类而言,将重新递归调用ViewGroup的onMeasure方法;
对于View及其子类而言,将调用View的onMeasure方法。
由于measureChildWithMargins会递归调用ViewGroup的onMeasure方法,
可以看出整个View的测量顺序是先序遍历的,但最终计算结果时是后序遍历的,
即子View测量完毕后,才能得到父View的size。
2.5 View的onMeasure
现在我们跟进一下View的onMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
从代码可以看出,普通View只需要完成自身的测量工作即可。
View以getDefaultSize方法的返回值来作为测量结果,通过setMeasuredDimension方法进行设置。
getDefaultSize的源码如下:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
三、总结
至此,我们大致了解了View的测量流程。
个人觉得重点在于了解MeasureSpec对测量过程的影响,
同时知道测量的顺序是先序遍历,计算最终结果是后序遍历即可。
此外,当父容器的宽或高为wrap_content,其子View的宽或高为match_parent时,
父容器得到最终的宽、高后,需要重新测量这部分子View。
下一篇博客,我们将继续关注View绘制的布局流程。