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

╰+攻爆jí腚メ 2022-07-14 08:52 344阅读 0赞

概述

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

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

  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. setContentView(R.layout.activity_main);
  5. }

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

PhoneWindow对象

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

  1. public void setContentView(@LayoutRes int layoutResID) {
  2. getWindow().setContentView(layoutResID);
  3. initWindowDecorActionBar();
  4. }

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

  1. mWindow = new PhoneWindow(this, window);
  2. public Window getWindow() {
  3. return mWindow;
  4. }

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方法,

  1. @Override
  2. public void setContentView(int layoutResID) {
  3. // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
  4. // decor, when theme attributes and the like are crystalized. Do not check the feature
  5. // before this happens.
  6. if (mContentParent == null) {
  7. installDecor();
  8. } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  9. mContentParent.removeAllViews();
  10. }
  11. if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  12. final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
  13. getContext());
  14. transitionTo(newScene);
  15. } else {
  16. mLayoutInflater.inflate(layoutResID, mContentParent);
  17. }
  18. final Callback cb = getCallback();
  19. if (cb != null && !isDestroyed()) {
  20. cb.onContentChanged();
  21. }
  22. }

installDecor方法

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

  1. private void installDecor() {
  2. if (mDecor == null) {
  3. mDecor = generateDecor();
  4. mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  5. mDecor.setIsRootNamespace(true);
  6. if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
  7. mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
  8. }
  9. }
  10. if (mContentParent == null) {
  11. mContentParent = generateLayout(mDecor);
  12. }
  13. }
  14. protected DecorView generateDecor() {
  15. return new DecorView(getContext(), -1);
  16. }

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

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

DecorView

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

  1. private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
  2. ....
  3. }

generateLayout方法

generateLayout方法如下:

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

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

  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. getWindow().requestFeature(Window.FEATURE_NO_TITLE);
  5. setContentView(R.layout.activity_main);
  6. }

而且该方法的调用必须是在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下的布局。

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:fitsSystemWindows="true"
  5. android:orientation="vertical">
  6. <ViewStub android:id="@+id/action_mode_bar_stub"
  7. android:inflatedId="@+id/action_mode_bar"
  8. android:layout="@layout/action_mode_bar"
  9. android:layout_width="match_parent"
  10. android:layout_height="wrap_content"
  11. android:theme="?attr/actionBarTheme" />
  12. <FrameLayout
  13. android:id="@android:id/content"
  14. android:layout_width="match_parent"
  15. android:layout_height="match_parent"
  16. android:foregroundInsidePadding="false"
  17. android:foregroundGravity="fill_horizontal|top"
  18. android:foreground="?android:attr/windowContentOverlay" />
  19. </LinearLayout>

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

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

  1. ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

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

  1. @Override
  2. public void setContentView(int layoutResID) {
  3. // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
  4. // decor, when theme attributes and the like are crystalized. Do not check the feature
  5. // before this happens.
  6. if (mContentParent == null) {
  7. installDecor();
  8. } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  9. mContentParent.removeAllViews();
  10. }
  11. if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  12. final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
  13. getContext());
  14. transitionTo(newScene);
  15. } else {
  16. mLayoutInflater.inflate(layoutResID, mContentParent);
  17. }
  18. final Callback cb = getCallback();
  19. if (cb != null && !isDestroyed()) {
  20. cb.onContentChanged();
  21. }
  22. }

总结

对于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的结构,也可以一一印证上面的结论。
这里写图片描述

发表评论

表情:
评论列表 (有 0 条评论,344人围观)

还没有评论,来说两句吧...

相关阅读