文章目录
  • 前几天在给一个同事讲&运算取变量的状态写法,原因是我看到他代码中这样写会产生问题,所以想纠正他一下,于是就跟他说你可以参考下android系统中View在measure时使用的状态是怎么通过&运算获取的。

  • 他也说这几个状态是与View在布局文件中设置width或height时,有个对应关系。就这个机会,深入的了解了下View的宽高的measure是怎么与android.view.View.MeasureSpec.EXACTLYandroid.view.View.MeasureSpec.AT_MOSTandroid.view.View.MeasureSpec.UNSPECIFIED这三种状态一一对应的。于是就有了下文。

在布局文件中设置View的layout_widthlayout_height时,可以设置三种值match_parentwrap_content和具体的长度值,下面看下android系统源码中是怎么处理这几种值的。

做过android开发都知道,如果在Activity的onCreate()方法中通过调用View的getWidth()方法是获取不到View的宽高的,此时返回的是0,因为View还没有初始化完成。那么查看View的getWidth()方法发现,返回的值是通过mRight - mLeft返回的值,也就是说如果调用getWidth()返回值,那么一定是mRightmLeft这两个变量被赋值了。View的getHeight()方法返回高度同理。在View中mLeft个成员被初始化值在两个方法中,一个是setLeft(int)方法中,一个是setFrame(int,int,int,int)中,实际上这两个方法都是由系统来调用的,尤其是setFrame()方法被标记为隐藏。通过查看源码发现,setLeft(int)这个方法很少被使用,倒是跟踪setFrame(int,int,int,int)方法时,发现一些眉目,最后发现这个这个setFrame()方法是在View的layout()方法中一直调用过来的。OK,那么这个layout()方法的调用轨迹是怎样的呢?可以自定义一个View,然后重写这个View的layout()方法,代码如下所示:

1
2
3
4
5
6
@Override
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r, b);
Throwable th = new Throwable();
th.printStackTrace(); // 打印被调用的栈信息
}

输出的LOG如下所示:

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
W/System.err: java.lang.Throwable
W/System.err: at com.jacpy.busline.widget.BusLineView.layout(BusLineView.java:243)
W/System.err: at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1055)
W/System.err: at android.view.View.layout(View.java:14860)
W/System.err: at android.view.ViewGroup.layout(ViewGroup.java:4643)
W/System.err: at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
W/System.err: at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
W/System.err: at android.view.View.layout(View.java:14860)
W/System.err: at android.view.ViewGroup.layout(ViewGroup.java:4643)
W/System.err: at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
W/System.err: at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
W/System.err: at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
W/System.err: at android.view.View.layout(View.java:14860)
W/System.err: at android.view.ViewGroup.layout(ViewGroup.java:4643)
W/System.err: at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
W/System.err: at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
W/System.err: at android.view.View.layout(View.java:14860)
W/System.err: at android.view.ViewGroup.layout(ViewGroup.java:4643)
W/System.err: at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2013)
W/System.err: at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1770)
W/System.err: at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1019)
W/System.err: at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5725)
W/System.err: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
W/System.err: at android.view.Choreographer.doCallbacks(Choreographer.java:574)
W/System.err: at android.view.Choreographer.doFrame(Choreographer.java:544)
W/System.err: at android.view.Choreographer$FrameDisplayEventReceiver.run(
W/System.err: at android.os.Handler.handleCallback(Handler.java:733)
W/System.err: at android.os.Handler.dispatchMessage(Handler.java:95)
W/System.err: at android.os.Looper.loop(Looper.java:136)
W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5086)
W/System.err: at java.lang.reflect.Method.invokeNative(Native Method)
W/System.err: at java.lang.reflect.Method.invoke(Method.java:515)
W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(
W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
W/System.err: at dalvik.system.NativeStart.main(Native Method)

这段LOG从下往上看,首先是ZygoteInit启动,也就是手机系统的启动,这部分忽略,只看与应用相关的,从ActivityThread.main()开始。在ActivityThread中启动的Looper主线程用来执行了一个Runnable实例,这个实例是android.view.Choreographer$FrameDisplayEventReceiver类的实例,在这个类的run()方法中执行了Choreographer的doFrame()方法,在这个方法中看到熟悉的日志输出:

1
2
Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
+ "The application may be doing too much work on its main thread.");

当View初始化时在主线程中做的操作过多导致卡帧时,这个LOG就会输出。

OK,跑题了,继续看日志。在doCallbacks()方法中从mCallbackQueues这个回调队列中根据回调的类型取出要执行的CallbackRecord对象,并调用其run()方法。而ViewRootImpl的TraversalRunnable对象是通过在ViewRootImpl的scheduleTraversals()方法中设置的。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); // 将mTranversalRunnable加入到mChoreographer的mCallbackQueues的队列中
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}

最后在performLayout()方法中看到具体调用View的layout()方法的代码:

1
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

这里的host是ViewRootImpl的mView对象,这个对象是一个PhoneWindow.DecorView对象,后面会有文章分析。

OK,这里可以看到mLeft赋值是0,mRight就是getMeasureWidth()方法的返回值。getMeasureWidth()这个方法代码如下所示:

1
2
3
4
5
6

public static final int MEASURED_SIZE_MASK = 0x00ffffff;

public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}

上面的代码可以看出,mMeasureWidth取了后三个字节,也就是本来是int类型的mMeasureWidth实际目前只使用了低位三个字节,对于目前的显示的分辨率来说足够。

OK,重点来了,这个mMeasureWidth的值是怎么来的?继续跟代码发现这个变量在View的setMeasuredDimensionRaw()方法中被赋值,使用方法中的参数赋值。接着发现这个方法在setMeasuredDimension()measure()方法中被调用。而在measure()方法中cacheIndex这个变量可能是-1,因为mMeasureCache变量中的键值对只在measure()方法最后才放进去的,而最后是调用了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
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// ...
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; // 用一个64位的long类型保存两个32位的值,高32位是宽,低32位是高
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {

// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
// ...
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key); // 如果没有找到这个Key也是返回-1
if (cacheIndex < 0 || sIgnoreMeasureCache) { // 极有可能是走这个判断
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}

// ...
}

// ...
// 保存键值对
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

setMeasuredDimension()方法是在onMeasure()方法中有调用。因此,上面的代码不管是走if流程还是else流程,最终都会调用setMeasuredDimensionRaw()这个方法。不管流程怎样,这个值是从measure()这个方法中的参数传进来的。以同样的方法重写View的onMeasure()方法,如下代码所示:

1
2
3
4
5
6
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Throwable t = new Throwable();
t.printStackTrace();
}

输出的LOG如下所示:

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
java.lang.Throwable
at com.jacpy.busline.widget.BusLineView.onMeasure(BusLineView.java:442)
at android.view.View.measure(View.java:16540)
at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:719)
at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:455)
at android.view.View.measure(View.java:16540)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5137)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.view.View.measure(View.java:16540)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5137)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1404)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:695)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:588)
at android.view.View.measure(View.java:16540)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5137)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2291)
at android.view.View.measure(View.java:16540)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1942)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1132)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1321)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1019)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5725)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
at android.view.Choreographer.doCallbacks(Choreographer.java:574)
at android.view.Choreographer.doFrame(Choreographer.java:544)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5086)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(Native Method)

从上面的LOG可以看出来,最终是在measure()方法中调到onMeasure()方法,也就是上面分析的流程。

通过上面两段LOG发现,最终都指向了同一个地方,那就是ViewRootImpl的performTraversals()。也就是说,在这个方法中先measure,然后再layout。

measureHierarchy()方法中可以看出来在调用performMeasure(int,int)方法时,传了两个参数childWidthMeasureSpecchildHeightMeasureSpec,而这两个变量是通过getRootMeasureSpec()方法返回的,如下代码所示:

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
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;

boolean goodMeasure = false;
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// On large screens, we don't want to allow dialogs to just
// stretch to fill the entire width of the screen to display
// one line of text. First try doing the layout at a smaller
// size to see if it will fit.
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": baseSize=" + baseSize);
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
// Didn't fit in that size... try expanding a bit.
baseSize = (baseSize+desiredWindowWidth)/2;
if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": next baseSize="
+ baseSize);
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) Log.v(TAG, "Good!");
goodMeasure = true;
}
}
}
}

if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}

return windowSizeMayChange;
}

本文的重点来了,getRootMeasureSpec()方法的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {

case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}

从方法中的switch可以看到,MATCH_PARENT对应MeasureSpec.EXACTLYWRAP_CONTENT对应MeasureSpec.AT_MOST,默认的可以认为是设置了具体的值对应的是MeasureSpec.EXACTLY

为了确定一下,可以看下MeasureSpc的makeMeasureSpec()方法的代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;

public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}

前面说尺寸值是由低三个字节表示,这里可以看到高位第一个字节的前两个位用来表示mode。也就是说int类型的第31、32位表示的是mode值,低30位表示是具体的长度值。

OK,最后是在ViewGroup的measureChildWithMargins()方法中调用每个child的measure()方法去具体测量每个子View的长度,代码如下所示:

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);
}

而其中被调用的getChildMeasureSpec()方法中子View的大小是根据父View的大小及模式来决定的,代码如下所示:

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
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) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

OK,以上就是整个分析过程。

文章目录