这篇主要想梳理一下真实的一个Demo它的measure、layout、draw到底是怎么样的一个调用过程。
前几天,我在嵌套在RelativeLayout中的自定义View的onMeasure()、onLayout()、onDraw()中打印信息,居然输出了四次measure两次layout,有一部分是因为RelativeLayout两次测量子View。另外就是只知道performTraversals()是三大流程的起点,但是不知道到底是怎么样调用的,所以.....
开始之前应该先复习下面这些基础知识。
一、调用过程的结论
API26从performTraversals()到三大流程的运行步骤如下。
第一次performTraversals()
- 第一个measureHierarchy()被调用。
- performMeasure()被调用。
- View的measure()被调用。
- DecorView的onMeasure()被调用,递归到底。(onMeasure第一次)
- 第一个performMeasure()被调用。
- View的measure()被调用。
- DecorView的onMeasure()被调用,递归到底。(onMeasure第二次)
- performLayout()被调用。
- layout()被调用
- onLayout()被调用,递归到底。(onLayout第一次)
- performDraw()没有被调用,开始了新一轮的performTraversals()。
第二次performTraversals()
- 第一个measureHierarchy()被调用。
- performMeasure()被调用。
- View的measure()被调用。
- 内层自定义View的onMeasure()没有被调用。
- performLayout()被调用。
- layout()被调用。
- 内层自定义View的onLayout()没有被调用。
- performDraw()被调用。
- draw()被调用,onDraw()被调用,递归到底。(onDraw第一次)
二、DecorView结构
测试的布局
Activtiy加载以下布局文件,其中Circle为继承View的自定义View。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.utte.viewevent.CircleView
android:layout_width="400dp"
android:layout_height="200dp" />
</LinearLayout>
使用的style是默认项目的AppTheme。
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
DecorView结构
如果使用上面的测试布局,DecorView在第一次测量时将会是如下结构:
DecorView(3)
- LinearLayout(2)
- ViewStub (GONE)
- FrameLayout(1)
- ActionBarOverlayLayout(2)
- ActionBarContainer(2)
- ToolBar(1)
- AppCompatTextView
- ActionBarContextView(GONE)
- ToolBar(1)
- ContentFrameLayout(1, 这里是Activity加载的布局)
- LinearLayout(1)
- 自定义的CircleView
- LinearLayout(1)
- ActionBarContainer(2)
- ActionBarOverlayLayout(2)
- View(id为navigationBarBackgroud)
- View(id为statusBarBackgroud)
注意
- ContentFrameLayout中的布局就是Activity中setContentView加载进去的布局。
- 这个DecorView并不是不变的,在后面的measure、layout过程中,可能会增加布局。
三、详细流程--第一次performTraversals()
1)第一个measureHierarchy()被调用
这是第一次测量,也是步骤最多最复杂的。所以分析得稍微详细些,后面的流程和这个都差不多。
1. measureHierarchy()被调用
调用条件是layoutRequested
为true。
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
// ......
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
mLayoutRequested在1629行由于mFirst为true,所以被赋值为true。
if (mFirst) {
// ......
mLayoutRequested = true;
// ......
}
mStopped初始化为false,并且一直为false,没有找到任何地方将它赋值为true。
mReportNextDraw暂时应该没有涉及到,所以默认为false。
最终layoutRequested为true,measureHierarchy()被调用。
2. ViewRootImpl#measureHierarchy() -> DecorView#measure()
在measureHierarchy()中,由于窗口宽不为wrap_content,所以1471和1484的performMeasure()没有被调用,并且goodMasure没有被赋值为true。1498的performMeasure()调用条件为goodMeasure为false,所以这个performMeasure()被调用了。
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// ......
if (baseSize != 0 && desiredWindowWidth > baseSize) {
// ......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// .......
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
// ......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
}
}
}
}
if (!goodMeasure) {
// ......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// ......
}
performMeasure中只要mView(此时为DecorView)不空,就会调用mView的measure()。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
// ......
}
3. DecorView的第一次测量过程
由于只有View实现了measure(),是final的方法,所以所有视图调用measure()都会直接到了View的measure()。调用mView(DecorView)的measure(),实际是调用了View统一实现的measure()。
View#measure() -> DecorView#onMeasure()
在measure()中调用onMeasure的条件是:
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// ......
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// ......
onMeasure(widthMeasureSpec, heightMeasureSpec);
// ......
sIgnoreMeasureCache值在API20之前一直为true,API20之后一直为false。此处forceLayout为true,所以onMeasure()被调用。
DecorView的测量分发
到达DecorView的onMeasure(),里面直接调用了其父类(FrameLayout)的onMeasure()。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// ......
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// ......
}
到达FrameLayout的onMeasure(),首先计算DecorView的子View个数,三个,一个LinearLayout和两个普通的View,分别对他们调用measureChildWithMargins(),measureChildWithMargins()会调用子View的measure()。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// ......
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
// ......
}
}
// ......
}
a. DecorView第一个子View--LinearLayout的测量
measureChildWithMargins()调用了LinearLayout的measure()(其实就是View的measure())。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
// ......
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
这个measure()的forceLayout是ture,所以调用到LinearLayout的onMeasure()。进入了LinearLayout的onMeasure()后,判断了orientation,进入了measureVertical(),LinearLayout有两个子View。
i. LinearLayout的第一个子View--ViewStub
第一个是ViewStub,但是这个ViewStub的状态是GONE的,于是没有做测量的工作。
ii. LinearLayout的第二个子View--FrameLayout
第二个子View是一个FrameLayout,一直调用到FrameLayout的onMeasure()。在onMeasure中得出FrameLayout子View只有一个,ActionBarOverlayLayout。
ActionBarOverlayLayout的onMeasure()被调用,测量过程会向两个子View传递,ActionBarContainer和ContentFrameLayout。
ActionBarContainer
ActionBarOverlayLayout对mActionBarTop调用了measureChildWithMargins(),mActionBarTop就是ActionBarContainer。ActionBarContainer的onMeasure()被调用,ActionBarContainer继承了FrameLayout(),在它的onMeasure()中调用了父类(FrameLayout)的onMeasre()。
ActionBarContainer还有两个子View,一个是Toolbar,Toolbar的onMeasure()被调用,Toolbar里面还有一个子View,AppCompatTextView,这个TextView的绘制过程就不说了。
接着走到了ActionBarContainer的第二个子View,是ActionBarContextView,它是GONE的状态。ActionBarContainer的两个子View就遍历完了。
ContentFrameLayout
接着ActionBarOverlayLayout对mContent调用measureChildWithMargins(),mContent是ContentFrameLayout,调用到ContentFrameLayout的onMeasure(),调用了它父类(FrameLayout)的onMeasure()。ContentFrameLayout有一个子View,是LinearLayout。
这个LinearLayout就是前面代码中我们自己定义的布局文件中的LinearLayout。进入measureVertical(),里面一个子View,是我自定义的CircleView。CircleView的onMeasure()被调用。ContentFrameLayout测量结束。
b. DecorView第二、三个子View的测量
后面两个子View都是普通的View,一个id为navigationBarBackgroud,一个为statusBarBackgroud。至此,DecorView的测量就完成了,回到了ViewRootImp()的performTraversals()。
2)第二个measureHierarchy()没有被调用
mApplyInsetsRequested为false。
if (mApplyInsetsRequested) {
mApplyInsetsRequested = false;
// ......
if (mLayoutRequested) {
// .......
windowSizeMayChange |= measureHierarchy(host, lp,
mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
}
}
3)第一个performMeasure()被调用
mStopped为false,updatedConfiguration为true。
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
// ......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
DecorView第二次测量流程和第一次一样。
4)第二个performMeasure()未被调用
weight都是0,measureAgain为false。
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
// ......
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
// ......
measureAgain = true;
}
if (measureAgain) {
// ......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
5)performLayout()会被调用
调用条件
layoutRequested为true,mStopped为false,mReportNextDraw为false。didLayout为true,所以performLayout被调用。
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
// ......
if (didLayout) {
performLayout(lp, mWidth, mHeight);
// ......
}
ViewRootImpl#performLayout()->DecorView#onLayout()
performLayout()中只要mView(DecorView)不空,就会调用到mView的layout(),DecorView没有重写layout(),所以调用到ViewGroup的layout()。ViewGroup调用了父类(View)的layout()。View的layout调用onLayout的条件如下,changed为true,DecorView的onLayout()被调用。
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
DecorView布局流程分发
DecorView的onLayout会直接调用父类(FrameLayout)的onLayout()。在这遍历了DecorView的子View,调用每一个子View的layout(),和measure流程类似的分发。
LinearLayout中没有重写layout(),所以调用了ViewGroup的layout()->View的layout()->LinearLayout的onLayout()->LinearLayout的onLayout()->LinearLayout的layoutVertical(),有两个子View,ViewStub还是不存在,之后会对第二个子View FrameLayout调用setChildFrame(),内部调用FrameLayout的layout()。然后调用到FrameLayout的onLayout(),继续分发到底。
和measure有一点点不同的是会先处理ContentFrameLayout,再处理ActionBarContainer。
6)performDraw()未被调用
newSurface为true。isViewVisible为true,performDraw()没有被调用。在这里调用了scheduleTraversals(),所以会有第二次performTraversals()。
if (!cancelDraw && !newSurface) {
// ......
performDraw();
} else {
if (isViewVisible) {
// Try again
scheduleTraversals();
}
// ......
}
四、详细流程--第二次performTraversals()
1) measureHierarchy()被调用
这里mLayoutRequested为true,mStopped为false,mReportNextDraw为true,因为在上一次performTraversals()时report了一次。
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
// ......
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
之后一样的goodMeasure为false,performMeasure()被调用。大体是一样的,但是为什么这次测量并没有进入自定义View的onMeasure呢,就是因为在调用ContentFrameLayout的measure()时,forceLayout是false,所以ContentFrameLayout的onMeasure()没有被调用。
另外一个和之前不同的是Toolbar中的onMeasure()会去调用ActionMenuView的onMeasure(),ActionMenuView在之前的两次测量中并没有出现。
2) 第二个measureHierarchy()没被调用
和第一次performTraversals一样的原因。
3) 第一、二个performMeasure()没被调用
mStopped为false,mReportNextDraw为true,但是第二个if没有进入,updatedConfiguration为false。
if (!mStopped || mReportNextDraw) {
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
// .......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// .......
if (measureAgain) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
4) performLayout()被调用
DecorView的其它部分还是有调用onLayout()的。除了到达ContentFrameLayout调用layout()时,在View的layout()中,changed为false,并且没有设置PFLAG_LAYOUT_REQUIRED,所以他的onLayout()没有被调用。
public void layout(int l, int t, int r, int b) {
// ......
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
// ......
}
// ......
}
5) performDraw()被调用
完成完整的draw流程。
五、总结
这样一分析,三大流程确实清楚了不少,最重要的是三大流程不只是几个单独的方法了,明白了调用流程。
没有想到简简单单的一个布局,居然这么复杂,主要还是这现成的theme结构复杂,早知道就换一个没有Toolbar的theme分析了......
表面上确实是performTraversals()调用了两次,并且方法内部很多measure()的调用,但是因为各种判断,让测量和布局的次数远没有写上去的那么多。
能力有限,还没有理解为什么要这样设计,做两次完整测量和一次不完整测量。
当程序员好累啊...我的颈椎...