setContentView源码解析
前言
我们经常编写的xml文件用于界面显示,但在我们设置了setContentView()后,你是否知道View是这么被解析,然后被绘制显示出来。这篇文章主要从setContentView切入,去查看DecorView加载、以及measure,layout等操作,如何分发到子View。相关源码来自android Api 28
setContentView相关流程
这是来自Activity的一段代码,Activity源码中setContentView()本身做了很多事,setContentView中主要是初始化DecorView,然后设置DecorView中的ActionBar。getWindow()获取的是其唯一子类PhoneWindow
,所以setContentView其实是由PhoneWindow
在执行。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
PhoneWindow
首先知道PhoneWindow是Window的唯一继承类;而每一个Activity都需要一个窗口去承载layout。那么具体的很多事就都由PhoneWindow来完成
setContentView
首先DecorView分为两部分一个是上面的ActionBar,还有一个就是ContentParent,ContentParent是一个ViewGroup,主要作为我们设置layout的容器。他们两个具体的创建位置就在PhoneWindow中。
下面代码来自PhoneWindow,其中关键就两步,
- 在mContentParent==null的时候、创建mContentParent和DecorView
- 将我们设置的layout添加到DecorView的mContentParent中
接下来我们继续看比较关键的installDecor
和 mLayoutInflater.inflate
方法。、
installDecor主要用于初始化DecorView和ContentParent,而mLayoutInflater.inflate主要用于将添加到ContentParent的layout映射成View,并添加到他们的父View中,也就是为什么我们能在ViewGroup中使用getChildView(i)
@Override
public void setContentView(int layoutResID) {
//onCreate方法一般情况下只会走一次,在第一次进入的时候mContentParent==null,那么要这时候初始化DecorView,
if (mContentParent == null) {
//初始化DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//上面判断是做功能验证
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 将Activity的视图添加到DecorView的mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}
installDecor
下面三段代码很好理解。在installDecor中,如果mDecor == null,在generateDecor()方法中New 一个DecorView;然后判断如果 mContentParent==null,为空则在generateLayout()方法中根据R.id.content
去findViewById创建一个ViewGroup,这个ViewGroup就是mContentParent 。到这里DecorView的创建就完成了,接下来就是将layout中的View映射出来,并添加到父View
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
}
}
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
...
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
}
mLayoutInflater.inflate
LayoutInflater.inflate其实我们并不陌生、但如果不懂知道它做了什么,可能就不明白在viewGroup.getChildView(i),其中的子View是怎么来的。
res.getLayout(resource)
是将LayoutRes转换成XmlResourceParser,
XmlResourceParser是一个动态解析XML文件的工具
,它将layout布局上的每一个节点进行分类,标记节点的深度,元素的名称空间URI,元素的开始和结束等,具体看官方文档
下面代码来自LayoutInflater:
主要步骤有三步
- 找到ContentView中的根布局的名字,根据name创建根View:
temp
- 调用
rInflateChildren()
->rInflate()
方法,遍历temp将ContentView中的xml映射成View - 映射完成后将ContentView中的根布局添加到ContentParent中,而具体的映射过程在
rInflate(...)
中完成
public abstract class LayoutInflater {
...
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
...
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
...
//将LayoutRes转换成XmlResourceParser
final XmlResourceParser parser = res.getLayout(resource);
return inflate(parser, root, attachToRoot);
...
}
...
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
View result = root;
try {
...
//1 获取layout中根布局的名字
final Sting name = parser.getName();
//判断xml节点是否使用的merge标签,将其中布局进行解析
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
//2 Temp is the root view that was found in the xml
//根据找到layout中的根布局名字,根据name创建layout中的根布局
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
...
if (root != null) {
params = root.generateLayoutParams(attrs);
}
...
//关键:深度遍历layout,将其中的View进行解析
rInflateChildren(parser, temp, attrs, true);
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
//关键:::为ContentParent添加layout生产的View:temp
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
...
rInflate(…)
具体的映射过程分3步:
- 外层是一个 while循环,将根节点下的子节点进行遍历
- 得到节点名称,根据节点名称调用
createViewFromTag()
创建View;得到View后让View执行这个映射1、2、3的过程 - 将得到的View添加到父布局
最后调用View的onFinishInflate()方法
,提示layout映射完成
。这里要明白,为什么是这样的一个映射过程,其实就是循环遍历,特别是在第2点上,当创建的View是一个容器类ViewGroup时,这个时候是需要再遍历ViewGroup中的子View,所以会重复1、2、3的过程
之前看DecorView的onMeasure过程的时候不明白,子View是什么时候添加到ViewGroup的,看到这里就明白了。其实所有的子View添加到ViewGroup都是在LayoutInflater中映射的时候完成。并且ViewGroup提供了很多对View的添加、删除,根据index操作View的方法。
...
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
...
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
//得到节点名称
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
//如果该节点为requestFocus
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
//如果该节点为tag
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
//如果该节点为include标签
throw new InflateException("<merge /> must be the root element");
} else {
//根据name创建View
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//如果当前view是一个容器,那么再做进一步的映射
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
...
}
initWindowDecorActionBar()
让我们再回到setContentView里面,在上面我们成功创建了DecorView,并将layout里面的View添加到了ContentParent里面,DecorView就完成了一部分,另一部分就是ActionBar。下面就是WindowDecorActionBar的创建过程,具体可以看WindowDecorActionBar的构造方法。
private void initWindowDecorActionBar() {
Window window = getWindow();
window.getDecorView();
if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
return;
}
mActionBar = new WindowDecorActionBar(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}
到这里我们可以得到一张关于DecorView的图:
当我们启动一个Activity的同时,会创建一个window的子类PhoneWindow;在Activity的onCreate中设置layout的时候,会在PhoneWindow中创建DecorView并映射layout布局文件。
图片来自网络
DecorView的测量与绘制
上面部分代码介绍了decorView的创建以及layout映射成View的过程,整个过程在onCreate中完成。但我们并没有看到对View进行测量等操作,并且我们知道在onResume之前我们是获取不到View的宽高的,所以带着问题我们去看看Activity生命周期中onResume方法。在ActivityThread
中有调用生命周期的方法handleResumeActivity()
,并且在其中发现了关于测量View的关键代码wm.addView(decor, l)
ActivityThread
performResumeActivity()
就是调用了Activity生命周期方法onResume
。在调用onResume()后调用wm.addView(decor, l),关键逻辑就在addView中。
在这个过程中涉及到其他几个类ViewManager
、WindowManager
,WindowManagerImpl
,
WindowManagerImpl实现接口WindowManager,WindowManager继承接口ViewManager
- ViewManager:只有三个方法addView、updateViewLayout、removeView,主要用于向Activity添加和删除子视图的界面
- WindowManager:通过Binder与WindowManagerService进行跨进程通信,把具体的实现工作交给WindowManagerService来完成
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
...
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;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
...
}
WindowManagerImpl
WindowManager和ViewManager的实现类,负责Window的添加、更新和删除操作。通过WindowManagerGlobal与WMS通信,这里将addView交给WindowManagerGlobal
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerGlobal
WindowManagerGlobal主要用于管理ViewRoot、创建并提供IWindowManager的proxy实例,在这里会创建ViewRootImpl
类,由ViewRootImpl控制View的测量和绘制。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
root = new ViewRootImpl(view.getContext(), display);
//为DecorView设置LayoutParams
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
//关键地方,调用ViewRootImpl的方法
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
ViewRootImpl
ViewRootImpl
是View的根,它控制了View的测量和绘制,同时持有WindowSession通过Binder与WMS通信,同时持有IWindow作为WSM的回调接口,用于例如touch事件的回调
在ViewRootImpl中经过一系列的方法调用,最后关键代码在performTraversals()
中,performTraversals里有三个方法performMeasure()
、performLayout()
、performDraw()
,接下来我们具体看关键方法performTraversals()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();
...
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
performTraversals
在performTraversals里面看到有调用两次performMeasure:在第一次 performMeasure() 方法调用后, 如果子View 需要的空间大于父容器为它测量的大小,那么对应的 verticalWeight 和 horizontalWeight 将会大于0,即这两个字段分别对应垂直和水平的情况下子 View 需要的额外空间。这时候会将 measureAgain 设置为 true, 并且开始第二次测量。
下面是measure、layout、draw的调用流程:
ViewRootImpl.performMeasure
—> View.measure
—> DecorView.onMeasure
ViewRootImpl.performLayout
—>View.layout
—>DecorView.onLayout
ViewRootImpl.performDraw
—>ViewRootImpl.draw
—>ThreadedRenderer.draw
—>ThreadedRenderer.updateRootDisplayList
—>ThreadedRenderer.updateViewTreeDIsplayList
—>View.updateDisplayListIfDirty
—>PhoneWindow$DecorView.draw
—>View.draw
—>ViewGroup.dispatchDraw
—>ViewGroup.drawChild
—>View.draw
并且在这里面我们看到了getRootMeasureSpec
方法返回childWidthMeasureSpec
,这个就是获取根视图的测量规范,我们可以看到这是用于对子View的宽度的约束,约束的子View就是DecorView。在performMeasure
中调用mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)
开始测量,这里的mView就是DecorView。相关的performLayout、performDraw也是如此。
到这里就开始了DecorView的测量,并且会从DecorView开始从上往下开始对子View进行测量。那么我们接下来看看这个流程是怎么传递下去的
private void performTraversals() {
...
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
//获取父DecorView的宽高MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
//对DecorView进行测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
//在第一次 performMeasure() 方法调用后, 如果子View 需要的空间大于父容器为它测量的大小,那么对应的 verticalWeight 和 horizontalWeight 将会大于0,
//即这两个字段分别对应垂直和水平的情况下子 View 需要的额外空间。这时候会将 measureAgain 设置为 true, 并且开始第二次测量
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
//重新进行测量
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
...
}
DecorView开始测量,调用measure方法,measure方法是一个静态方法不能重写,只有View类有这个方法。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
View
调用onMeasure()方法,这个方法DecorView有重写,那么会走DecorView的onMeasure()
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
...
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
...
}
看下面DecorView中onMeasure()代码,在前面我们调用int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
为DecorView生成过MeasureSpec,当MeasureSpec为AT_MOST模式的时候就对原来的widthMeasureSpec和heightMeasureSpec进行转换成精确模式EXACTLY,重新生成新的MeasureSpec,并调用其父类的FrameLayout的onMeasure()。
对于MeasureSpec的讲解在下一章View的Measure中会讲到。
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
final boolean isPortrait =
...
//如果不是EXACTLY的,这里还是要获取下视图的大小,让测量规格是EXACTLY
if (widthMode == AT_MOST) {
final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor : mWindow.mFixedWidthMajor;
if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
final int w;
if (tvw.type == TypedValue.TYPE_DIMENSION) {
w = (int) tvw.getDimension(metrics);
} else if (tvw.type == TypedValue.TYPE_FRACTION) {
w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
} else {
w = 0;
}
if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed width: " + w);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (w > 0) {
// 这里重新设置了测量规格
// 大小是performMeasure中给出的大小和自己获取的大小的最小值
widthMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.min(w, widthSize), EXACTLY);
fixedWidth = true;
} else {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(
widthSize - mFloatingInsets.left - mFloatingInsets.right,
AT_MOST);
mApplyFloatingHorizontalInsets = true;
}
}
}
mApplyFloatingVerticalInsets = false;
if (heightMode == AT_MOST) {
final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor
: mWindow.mFixedHeightMinor;
if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
final int h;
if (tvh.type == TypedValue.TYPE_DIMENSION) {
h = (int) tvh.getDimension(metrics);
} else if (tvh.type == TypedValue.TYPE_FRACTION) {
h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
} else {
h = 0;
}
if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed height: " + h);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (h > 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.min(h, heightSize), EXACTLY);
} else if ((mWindow.getAttributes().flags & FLAG_LAYOUT_IN_SCREEN) == 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(
heightSize - mFloatingInsets.top - mFloatingInsets.bottom, AT_MOST);
mApplyFloatingVerticalInsets = true;
}
}
}
getOutsets(mOutsets);
if (mOutsets.top > 0 || mOutsets.bottom > 0) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
if (mode != MeasureSpec.UNSPECIFIED) {
int height = MeasureSpec.getSize(heightMeasureSpec);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(
height + mOutsets.top + mOutsets.bottom, mode);
}
}
if (mOutsets.left > 0 || mOutsets.right > 0) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
if (mode != MeasureSpec.UNSPECIFIED) {
int width = MeasureSpec.getSize(widthMeasureSpec);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(
width + mOutsets.left + mOutsets.right, mode);
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
}
FrameLayout
在FrameLayout中才根据子类的相关信息以及
新生成新的MeasureSpec确定了DecorView的宽高值。同时遍历子View,为子View生成childWidthMeasureSpec和childHeightMeasureSpec。最后调用子View的测量child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
到这里DecorView的测量以及子View的MeasureSpec都已经生成。到后面不管ChildView是什么,都能在onMeasure中得到了布局约束,然后在布局约束指导下进行测量。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
//如果宽高中有一个不是精确模式,那么就返回true;如果是DecorView传递过来的,必定都是精确模式,为false
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());
//这里如果是DecorView过来的,那么并不会走下面方法,只有当使用FrameLayout,并且宽高中使用了warp_content,但子布局为MATCH_PARENT下面才会执行
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));
//计算childWidthMeasureSpec和childHeightMeasureSpec并调用child.measure
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);
}
}
}
到这里我们就讲解完了DecorView的创建和测量过程,将关键点和方法做一个小节
- PhoneWindow在PhoneWindow的setContentView方法中创建
- View映射过程是在LayoutInflater的rInflate()方法中,映射完成后将View添加到DecorView的ContentParent里
- DecorView开始测量的关键代码在ActivityThread的performResumeActivity()中,调用的ViewManager.addView(decor, l)
- 最后开始测量是在ViewRootImpl的performTraversals()里,,performTraversals中有三个方法performMeasure()、performLayout()、performDraw()
- DecorView继承FrameLayout,在FrameLayout的onMeasure()中调用measureChildWithMargins()对子View进行测量。