Android Activity的UI绘制流程之setContentView方法详解

概述

对于Android开发人员来说,想必对setContentView方法不会陌生,每当我们创建一个Activity时,都会重写该Activity的onCreate方法,在该方法中我们必须要调用setContentView方法来显示我们指定的布局或者View。那么setContentView方法又是如何将我们指定的布局或者View放入到指定的Activity中显示出来的呢?

今天这篇博客主要就是讲解Activity的UI绘制流程,而setContentView就是Activity的UI绘制起始过程。

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
}

这里先贴一张图,便于大家更好的理解这个流程
这里写图片描述

PhoneWindow对象

当我们创建一个Activity时,该类都是默认继承自Activity方法,所以首先我们需要进入到Activity方法中,然后找到该类的setContentView方法,该方法还有几个重载的方法,这里我们主要讨论的是定义资源id参数layoutResID的方法,代码如下:

public void setContentView(@LayoutRes int layoutResID) {
   getWindow().setContentView(layoutResID);
   initWindowDecorActionBar();
}

通过这个方法我们可以看出,在该方法中调用了 getWindow().setContentView(layoutResID),那么这个getWindow又是什么呢?

mWindow = new PhoneWindow(this, window);

public Window getWindow() {
    return mWindow;
}

setContentView方法

通过源码源码可知,getWindow方法返回的时一个Window类对象,而Window是系统定义一个抽象类,在Activity中我们实例化的是Window的一个实现类PhoneWindow。所以我们需要进入到PhoneWindow类中,查看setContentView方法具体实现。

但是通过Eclipse或者Android studio是不能直接查看到PhoneWindow的源代码的,要想查看该类的源码需要去sdk的源码中查找,具体路径为: /sdk/sources/android-21/com/android/internal/policy/impl/PhoneWindow.java。

进入该类之后找到setContentView方法,

@Override
public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        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 {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

installDecor方法

通过源码可知,当第一次初始化的时候会执行installDecor方法,该方法源码如下:

private void installDecor() {
     if (mDecor == null) {
         mDecor = generateDecor();
             mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
         mDecor.setIsRootNamespace(true);
         if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
             mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
         }
     }
     if (mContentParent == null) {
         mContentParent = generateLayout(mDecor);
     }
}
protected DecorView generateDecor() {
    return new DecorView(getContext(), -1);
}

在installDecor方法中首先会判断mDecor对象是否为空,如果为空则会调用generateDecor方法,generateDecor方法很简单就是生成一个DecorView对象。

在mDecor对象在执行完成之后又会判断mContentParent对象是否为空,如果为空则会通过generateLayout方法生成mContentParent,大家注意这个时候mDecor是作为参数传入到generateLayout方法中,不用质疑在该方法中会对mDecor做一些操作,那么或许有人会疑问这个DecorView又是什么呢?

DecorView

DecroView,其实就是PhoneWindow对象的一个内部类,该类继承自FrameLayout,对于DecorView,这里先不做过多的详细介绍,大家只需要知道DecroView其实就是作为Activity的顶级布局显示出来的就可以了。

private final class DecorView extends FrameLayout   implements RootViewSurfaceTaker {
    ....
}

generateLayout方法

generateLayout方法如下:

protected ViewGroup generateLayout(DecorView decor) {
  //1,获取<Application android:theme=""/>, <Activity/>节点指定的themes或者代码requestWindowFeature()中指定的Features, 并设置
  TypedArray a = getWindowStyle();
  //...
  
  //2,获取窗口Features, 设置相应的修饰布局文件,这些xml文件位于frameworks/base/core/res/res/layout下
  int layoutResource;
  int features = getLocalFeatures();
  if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
    if (mIsFloating) {
      TypedValue res = new TypedValue();
      getContext().getTheme().resolveAttribute(com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);
      layoutResource = res.resourceId;
    } else {
      layoutResource = com.android.internal.R.layout.screen_title_icons;
  }
  removeFeature(FEATURE_ACTION_BAR);
  } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
  layoutResource = com.android.internal.R.layout.screen_progress;
  //...
  
  mDecor.startChanging();
  //3, 将上面选定的布局文件inflate为View树,添加到decorView中
  View in = mLayoutInflater.inflate(layoutResource, null);
  decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
  //将窗口修饰布局文件中id="@android:id/content"的View赋值给mContentParent, 后续自定义的view/layout都将是其子View
  ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
  if (contentParent == null) {
    throw new RuntimeException("Window couldn't find content container view");
  }
  //...
}

在该方法中,首先会通过getWindowStyle方法获取window的样式闲逛属性并对Window进行一系列的初始化,这里大家可以看看代码,方法开始时这里通过一系列的判断调用requestFeature方法,对于requestFeature方法想必大家也不陌生,在开发中我们会经常在activity中调用该方法来设置FEATURE_NO_TITLE等属性。

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   getWindow().requestFeature(Window.FEATURE_NO_TITLE);
   setContentView(R.layout.activity_main);
}

而且该方法的调用必须是在setContentView方法之前,或许在一开始大家并不明白为什么要在setContentView方法之前调用requestFeature方法,但是现在应该就明白了。因为在setContentView方法中会间接的初始化Window的属性,这里会调用requestFeature等方法,如果在开发中我们在setContentView方法之后调用requestFeature方法改变一些属性值,那么此时window的初始化已经完成,在调用requestFeature方法就没有作用了。

接下来,在generateLayout方法中,mLayoutInflater会根据layoutResource创建一个View对象,这个View对象in会被放入到decor中,也就是之前创建的DecroView中。

而layoutResource的获取则和之前requestFeature方法初始化有关,这里我们分析下默认情况下layoutResource的值,也就是 layoutResource = R.layout.screen_simple;,这里我们可以看看screen_simple的布局,该布局的文件位置在sdk/platforms/android-21/data/res/layout/screen_simple.xml。当然,如果大家有兴趣也可以看看其他情况下如actionbar下的布局。


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

在该布局中的FrameLayout的id为content,想必大家就不陌生了,我们通过setContentView方法指定的布局最终就是给这个FrameLayout添加一个子布局。

最后,对DecorView添加完view对象之后,会通过该一下代码获取一个ViewGroup对象,这个对象就是generateLayout方法按最终返回的ViewGroup对象。

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

最终我们在回到setContentView方法中,在该方法中我们会填充指定给activity的布局,并且将mContentParent作为view的跟布局,而这个mContentParent就是通过generateLayout方法创建的。

@Override
public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        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 {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

总结

对于setContentView方法的分析就到此为止,这里我们总结一下:

  1. 当我们创建一个Activity时,会有一个PhoneWindow的对象被创建,PhoneWindow是抽象类Window的具体实现

  2. 在PhoneWindow中有一个内部类DecorView,DecroView是继承自FrameLayout,该类是所有应用窗口的根View

  3. 在DecorView中会添加一个具体的View,该View会根据不同的theme和feature而不同,但是有一个共同点就是在该View中会有一个id为”@android:id/content”的FrameLayout存在,该类将作为activity中显示指定布局的父布局存在,也就是activity显示的布局江北添加到这个FrameLayout中

最后贴上一张图,通过hierarchyviewer工具获取的activity的view的结构,也可以一一印证上面的结论。
这里写图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章