Android setContentView与LayoutInflater加载解析机制源码分析
  • Activity 和 Window之间的关系
  • 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();
  }
  • Activity 内维护了一个Window --
    Window 是一个抽象类 有个实现类 PhoneWindow PhoneWindow 内部维护了一个DecorWindow
Window是一个抽象类,提供了绘制窗口的一组通用API。

  PhoneWindow是Window的具体继承实现类。而且该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。

  DecorView是PhoneWindow的内部类,是FrameLayout的子类,是对FrameLayout进行功能的修饰(所以叫DecorXXX),是所有应用窗口的根View
  • 窗口PhoneWindow类的setContentView方法
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)) {
  		//应用程序里可以多次调用setContentView()来显示界面,因为会removeAllViews。
          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();
      }
  }

  -->
  private void installDecor() {
      if (mDecor == null) {
  		//首先判断mDecor对象是否为空,如果为空则调用generateDecor()
          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);
          //......
          //初始化一堆属性值
      }
   }

  -->
  protected DecorView generateDecor() {
 		 return new DecorView(getContext(), -1);
  }
  generateDecor()创建一个DecorView(该类是 FrameLayout子类,即一个ViewGroup视图);
  -->
  //generateDecor执行完之后会执行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;
   }

  根据窗口的风格修饰类型为该窗口选择不同的窗口根布局文件。mDecor做为根视图将该窗口根布局添加进去,然后获取id为content的FrameLayout返回给mContentParent对象。所以installDecor方法实质就是产生mDecor和mContentParent对象。
  我们平时requestWindowFeature()设置的值就是在这里通过getLocalFeature()获取的;而android:theme属性也是通过这里的getWindowStyle()获取的。
  • 总结过程
  • 创建一个DecorView的对象mDecor,该mDecor对象将作为整个应用窗口的根视图。

*依据Feature等style theme创建不同的窗口修饰布局文件,并且通过findViewById获取Activity布局文件该存放的地方(窗口修饰布局文件中id为content的FrameLayout)。

  • 将Activity的布局文件添加至id为content的FrameLayout内。
  • 至此整个setContentView的主要流程就分析完毕。
View二三
  • LayoutInflator
    可以将一个布局达成一个View对象,该类会调用一系列方法,底层使用Pull解析 解析出每个View的TAg 调用createViewFromTag(),该方法又会调用createView 方法 通过反射的方式 创建出View的对象并返回。
  • LayoutInflator 打出来的布局 为什么宽高属性会失效
    首先View必须存在于一个布局中,之后如果将layout_width 设置成 match_parent表示让View的宽度填充满布局,如果 设置成 wrap_content表示让View的宽度刚好可以包含其内容,如果设置成具体的数值则View的宽度会变成相应的数值。这也是为什么这两个属性叫作layout_width和layout_height,而不是width和height。
    这主要是因为,在setContentView()方法中,Android会自动在布局文件的最外层再嵌套一个FrameLayout,所以layout_width和layout_height属性才会有效果