Android setContentView 源码分析

基于Android5.1.1(API 22)分析

Activity的setContentView方法解析

Activity的源码中提供了三个重载的setContentView方法,如下:

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

  public void setContentView(View view) {
      getWindow().setContentView(view);
      initWindowDecorActionBar();
  }

  public void setContentView(View view, ViewGroup.LayoutParams params) {
      getWindow().setContentView(view, params);
      initWindowDecorActionBar();
  }

先调用了getWindow()的setContentView方法,然后调用Activity的initWindowDecorActionBar方法

关于窗口Window类的一些关系

Activity, Window, PhoneWindow, DecorView直接的关系

  • Activity中有一个成员为Window,其实例化对象为PhoneWindow,PhoneWindow为抽象Window类的实现类。
  • PhoneWindow内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。
  • DecorView是FrameLayout的子类,是对FrameLayout进行功能的修饰(所以叫DecorXXX),是所有应用窗口的根View 。

PhoneWindow类的setContentView方法

可以看见Window类的setContentView方法都是抽象的。所以我们直接先看PhoneWindow类的setContentView(int layoutResID)方法源码,如下:

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()方法,否则判断是否设置FEATURE_CONTENT_TRANSITIONS Window属性(默认false),如果没有就移除该mContentParent内所有的所有子View;
  • 接着mLayoutInflater.inflate(layoutResID, mContentParent);将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中
  • setContentView(View view, ViewGroup.LayoutParams params)方法,可以看见该方法与setContentView(int layoutResID)类似,只是少了LayoutInflater将xml文件解析装换为View而已,这里直接使用View的addView方法追加道了当前mContentParent而已。

窗口PhoneWindow类的installDecor方法

在PhoneWindow中查看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) {
           //根据窗口的风格修饰,选择对应的修饰布局文件,并且将id为content的FrameLayout赋值给mContentParent
           mContentParent = generateLayout(mDecor);
           //......
           //初始化一堆属性值
       }
   }
  • generateDecor()创建一个DecorView(该类是
    FrameLayout子类,即一个ViewGroup视图),然后设置一些属性;
  • 当mContentParent对象== null调用generateLayout()方法去创建mContentParent对象,
    generateLayout方法:
protected ViewGroup generateLayout(DecorView decor) {
      // Apply data from current theme.

      TypedArray a = getWindowStyle();

      //......
      //依据主题style设置一堆值进行设置

      // Inflate the window decor.

      int layoutResource;
      int features = getLocalFeatures();
      //......
      //根据设定好的features值选择不同的窗口修饰布局文件,得到layoutResource值

      //把选中的窗口修饰布局文件添加到DecorView对象里,并且指定contentParent值
      View in = mLayoutInflater.inflate(layoutResource, null);
      decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
      mContentRoot = (ViewGroup) in;

      ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
      if (contentParent == null) {
          throw new RuntimeException("Window couldn't find content container view");
      }

      //......
      //继续一堆属性设置,完事返回contentParent
      return contentParent;
  }
  • 根据设定好的features值选择不同的窗口修饰布局文件(比如各类标题栏,actionbar等等的布局),得到layoutResource值
  • 把选中的窗口修饰布局文件添加到DecorView对象里,并且指定contentParent值
  • installDecor方法实质就是产生mDecor和mContentParent对象。
番外,还记得我们平时写应用Activity时设置的theme或者feature吗(全屏啥的,NoTitle等)?我们一般是不是通过XML的android:theme属性或者java的requestFeature()方法来设置的呢?譬如:
通过java文件设置:

requestWindowFeature(Window.FEATURE_NO_TITLE);

通过xml文件设置:

android:theme="@android:style/Theme.NoTitleBar"
  • 我们平时requestWindowFeature()设置的值就是在这里通过getLocalFeature()获取的;而android:theme属性也是通过这里的getWindowStyle()获取的。 就明白在java文件设置Activity的属性时必须在setContentView方法之前调用requestFeature()方法的原因了吧。

setContentView还会调用一个Callback接口的成员函数onContentChanged来通知对应的Activity组件视图内容发生了变化。

PhoneWindow类的setContentView方法中最后调运了onContentChanged方法。我们这里看下setContentView这段代码,如下:

public void setContentView(int layoutResID) {
       ......
       final Callback cb = getCallback();
       if (cb != null && !isDestroyed()) {
           cb.onContentChanged();
       }
   }
  • 所以当我们写App时,Activity的各种View的findViewById()方法等都可以放到该方法中,系统会帮忙回调。

setContentView源码分析总结

  • 创建一个DecorView的对象mDecor,该mDecor对象将作为整个应用窗口的根视图。
  • 依据Feature等style theme创建不同的窗口修饰布局文件,并且通过findViewById获取Activity布局文件该存放的地方(窗口修饰布局文件中id为content的FrameLayout)。
  • 将Activity的布局文件添加至id为content的FrameLayout内

setContentView完以后Activity显示界面初探

简单说明setContentView之后怎么被显示出来的(注意:Activity调运setContentView方法自身不会显示布局的)。

  • 一个Activity的开始实际是ActivityThread的main方法
  • 启动Activity调运完ActivityThread的main方法之后,接着调用ActivityThread类performLaunchActivity来创建要启动的Activity组件,在创建Activity组件的过程中,还会为该Activity组件创建窗口对象和视图对象;
  • Activity组件创建完成之后,通过调用ActivityThread类的handleResumeActivity将它激活。
final void handleResumeActivity(IBinder token,
           boolean clearHide, boolean isForward, boolean reallyResume) {
       // If we are getting ready to gc after going to the background, well
       // we are back active so skip it.
       ......
       // TODO Push resumeArgs into the activity for consideration
       ActivityClientRecord r = performResumeActivity(token, clearHide);

       if (r != null) {
           ......
           // If the window hasn't yet been added to the window manager,
           // and this guy didn't finish itself or start another activity,
           // then go ahead and add the window.
           ......
           // If the window has already been added, but during resume
           // we started another activity, then don't yet make the
           // window visible.
           ......
           // The window is now visible if it has been added, we are not
           // simply finishing, and we are not starting another activity.
           if (!r.activity.mFinished && willBeVisible
                   && r.activity.mDecor != null && !r.hideForNow) {
               ......
               if (r.activity.mVisibleFromClient) {
                   r.activity.makeVisible();
               }
           }
           ......
       } else {
           // If an exception was thrown when trying to resume, then
           // just end this activity.
           ......
       }
   }

我们看下Activity的makeVisible方法,如下:

void makeVisible() {
       if (!mWindowAdded) {
           ViewManager wm = getWindowManager();
           wm.addView(mDecor, getWindow().getAttributes());
           mWindowAdded = true;
       }
       mDecor.setVisibility(View.VISIBLE);
   }
  • 通过DecorView(FrameLayout,也即View)的setVisibility方法将View设置为VISIBLE,至此显示出来。

总结

总结列表

参考

工匠若水

你可能感兴趣的:(Android setContentView 源码分析)