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属性才会有效果