从我们学习Android开发的第一天开始,我们就知道在Activity#onCreate里调用setContentView,Activity就会根据XML布局文件来显示。那么setContentView这个方法里究竟做了什么事情,隐藏了什么秘密,这篇文章带你一探究竟。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
首先看一下setContentView方法。这个方法很简单,主要是调用了getWindow方法,获取mWindow对象,然后调用了它的setContentView方法。mWindow对象是attach方法中创建出来的PhoneWindow实例。接下来分析PhoneWindow#setContentView方法。
96 @Override
397 public void setContentView(int layoutResID) {
398 // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
399 // decor, when theme attributes and the like are crystalized. Do not check the feature
400 // before this happens.
401 if (mContentParent == null) {
402 installDecor();//1
403 } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
404 mContentParent.removeAllViews();
405 }
406
407 if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
408 final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
409 getContext());
410 transitionTo(newScene);
411 } else {
412 mLayoutInflater.inflate(layoutResID, mContentParent);//2
413 }
414 mContentParent.requestApplyInsets();
415 final Callback cb = getCallback();
416 if (cb != null && !isDestroyed()) {
417 cb.onContentChanged();
418 }
419 mContentParentExplicitlySet = true;
420 }
从①号代码出可以看出,当mContentParent为空时则调用installDecor方法。mContentParent是个ViewGroup对象,从②代码可以看出mContentParent就是我们设置的布局的父布局。
2614 private void installDecor() {
2615 mForceDecorInstall = false;
2616 if (mDecor == null) {
2617 mDecor = generateDecor(-1);//1
2618 mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
2619 mDecor.setIsRootNamespace(true);
2620 if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
2621 mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
2622 }
2623 } else {
2624 mDecor.setWindow(this);//2
2625 }
2626 if (mContentParent == null) {
2627 mContentParent = generateLayout(mDecor);//3
//省略代码
2747 }
2748 }
以上是installDecor方法的部分代码,其中省略了一些初始化工作代码。从①号代码看到,如果mDecor对象为空则调用generateDecor方法创建DecorView,否则把当前类的引用传递给DecorView。DecorView是一个继承自FrameLayout的ViewGroup,包括title和content两部分,而我们设置的布局则是content里的子元素。从③号代码可以看到,调用generateLayout来给mContentParent赋值。generateLayout方法太长,在这不再贴出。主要是根据主题样式来设置DecorView的风格,然后为DecorView添加子view,这个子view就是mContentParent。
到目前为止,通过调用了setContentView,创建出了DecorView对象,并且把我们的布局文件添加至DecorView中成为子view。但是view没有经过测量、布局和绘制流程的话,是无法显示出来的。所以为了将DecorView显示出来,在ActivityThread#handleResumeActivity方法中,会把DecorView add到Window当中。WindowManager是抽象类,实现类是WindowManagerImpl。在WindowManagerImpl#addView方法中,又调用了WindowManagerGlobal#addView方法。
263 public void addView(View view, ViewGroup.LayoutParams params,
264 Display display, Window parentWindow) {
265 //省略代码
289 ViewRootImpl root;
290 View panelParentView = null;
291
292 synchronized (mLock) {
293 // Start watching for system property changes.
294 //省略代码
331 root = new ViewRootImpl(view.getContext(), display);
333 view.setLayoutParams(wparams);
334
335 mViews.add(view);
336 mRoots.add(root);
337 mParams.add(wparams);
338 }
339
340 // do this last because it fires off messages to start doing things
341 try {
342 root.setView(view, wparams, panelParentView);
343 } catch (RuntimeException e) {
344 // BadTokenException or InvalidDisplayException, clean up.
345 synchronized (mLock) {
346 final int index = findViewLocked(view, false);
347 if (index >= 0) {
348 removeViewLocked(index, true);
349 }
350 }
351 throw e;
352 }
353 }
以上是addView源码,其中省略掉了部分逻辑。可以看到addView方法的关键在于根据传递进来的参数view,生成一个ViewRootImpl实例,保存在mRoot数组中,并将view保存在mView数组中,最后调用ViewRootImpl#setView方法,最终完成了将DecorView添加至Window的工作。最后WindowManagerService调用ViewRootImpl#performTraversals方法来开始View的测量、布局和绘制三大流程,从而最终将View显示出来。
以上是我们在Activity调用了setContentView之后发生的一系列的事的简单总结。总结起来就是:调用了setContentView之后,系统会创建出一个DecorView,并且将我们设置的布局文件添加成为子元素。之后,在ActivityThread#handleResumeActivity方法中,系统将DecorView添加至Window当中,之后,系统在调用ViewRootImpl#performTraversals完成对View的测量、布局和绘制三大流程之后,View就会显示在屏幕上。