在Android 系统中,所有的控件父类都是android.view.View这个类,

可以看到View的直接子类和非直接子类是非常的多,而我们所熟悉的android.view.ViewGroup

可以看到ViewGroup还是继承View的,而它的子类也是非常常见的各种XXLayout 类
所以在Android系统中,控件大致可以分为两种,View 和 ViewGroup ,它们在屏幕上都是占据一块矩形。ViewGroup根据其名字就能猜到它是View的一组集合,也就是它可以包含多个View ,当然 它也可以包含ViewGroup,所以我们平常开发时候,就会在屏幕上形成一棵控件树

上层控件负责下层控件的测量和绘制,并传递交互事件,我们经常使用的findViewById()就是在控件树中以树的深度优先遍历来查找.。每一棵控件树的顶部都有一个顶级的ViewGroup,是控制树的核心,所有的交互事件都有它统一调度和分配。
那么android系统是如何将我们写的布局文件xxx.xml放置到屏幕上的尼?
先看一张android系统的界面控件架构

先大致说一下上面这张架构图,
在Activity.java中有这么一段注释
* An activity is a single, focused thing that the user can do. Almost all
* activities interact with the user, so the Activity class takes care of
* creating a window for you in which you can place your UI with
* {@link #setContentView}.大致意思就是Activity是用于与用户交互的,每一个Activity都是独立的,所以每一个Activity都会创建一个Window 对象来让你通过setContentView 放置你的UI ,然后我们
在Activity .java 真的发现了这么一个变量
private Window mWindow;但是查看WIndow类却发现它是一个抽象类,不能new 一个对象出来,那么它是如何创建出一个窗口给我们的尼?
在Activity类中查找发现

而PhoneWindow类
public class PhoneWindow extends Window implements MenuBuilder.Callback {是继承鱼Window类的,到此为止,我们知道一个Activity 中包含了一个一个PhoneWindow对象,那么我们现在回到最初的问题,xxx.xml如何加载到屏幕上
简单的列子
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}我们通过Activity的setContentView()方法将我们的布局文件传了上去,点击进去进一步查看Activity的setContentView方法
Activity.java中
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}发现这个方法是调用了getWindow()方法拿到一个对象,然后再用这个对象的setContentView方法,将布局文件inflate,
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}返回的是一个名叫mWindow 的 Window对象,通过前面我们知道这个mWindow其实是一个PhoneWIndow对象,所以我们下一步去到PhoneWindow中看看setContentView方法
在介绍PhoneWindow的setContentView方法之前我先介绍一下三个比较重要的变量,
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;
private ViewGroup mContentRoot;
DecorView 是PhoneWindow的一个内部类,继承于FrameLayout
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker再看看下面三张图



可以发现这三个变量都只有一处赋值,mDecor ,mContentParent 是在installDecor方法内被赋值,而mContentRoot是在generateLayout(DecorView)方法内被赋值,也就是说
当这个Activity第一次调用setContentView方法,在这些方法执行之前mDecor, mContentParent ,mContentRoot的值都是为null的(本笔记也是基于Activity第一次调用setContentView方法)
好,现在回到介绍PhoneWindow.java文件中的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)) {
mContentParent.removeAllViews();
}可以看到先是判断mContentParent是否为空,根据上面所说,mContentParent为空,进入installDecor(),这个方法就是mDecor, mContentParent赋值的方法(上面提到)
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();//return new DecorView(getContext(), -1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
在installDecor方法中,判断mDecor是否为空,为空,然后
mDecor = generateDecor();mDecor就是在此被赋值,
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}可以发现返回的 只是简单的new出来的一个DecorView对象,到此之后mDecor就被赋值了不再为空
那么再回到这里(上面再一次复制过来方便看)
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();//return new DecorView(getContext(), -1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);之后便是对mDecor进行各种属性的设置,先不用管
来到
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);mContentParent 还没赋值,所以进入这个if语句中
看看generateLayout执行了什么,(mContentRoot在这个方法被赋值!!!!!!)
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//根据主题设定相应的参数
TypedArray a = getWindowStyle();//获得window 的 style
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}.。。。。。。这里省略一大部分主题设置的相关代码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");
}
可以看到这里将一个layoutResource 参数inflate 那么这个layoutResource

可以看到layoutResource 赋值是根据不同的features ,这里就是我主题里平常设置的,比如 NO_TITLE / FULL_SREEN 什么的 它就根据这个将相应的系统预设xml文件赋值给layoutResource(int整形) 比如我们打开screen_simple.xml
<LinearLayout xmlns:android="http:///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>其实都是简单的布局文件
回到这里
View in = mLayoutInflater.inflate(layoutResource, null);//获取layoutResource布局,将xml布局转换成VIEW
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));将XML资源为layoutResource的布局添加到decor容器里面,
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}看到这个
mContentRoot = (ViewGroup) in;
layoutResource 最底层的ViewGroup ,如 screen_simple.xml
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);这里就是在这个
layoutResource 中找一个id为 ID_ANDROID_CONTENT 每个
layoutResource
至此,就完成了xml加载到Activity中工作
至于
requestWindowFeature(Window.FEATURE_NO_TITLE);这个方法,
public class MainActivity extends Activity {时,能成功设置
在
public class MainActivity extends AppCompatActivity通过
this.supportRequestWindowFeature(Window.FEATURE_NO_TITLE);来设置
因为在AppCompatActivity中
/**
* Enable extended window features. This should be called instead of
* {@link .Activity#requestWindowFeature(int)} or
* {@link android.view.Window#requestFeature getWindow().requestFeature()}.
*
还有就是想到一个问题,通过代码
requestWindowFeature(Window.FEATURE_NO_TITLE);和通过主题NoTitle 到底有什么不一样,最后查资料和看源代码,发现其实最后调用的都是Window的requestFeature方法,
至于为什么要在setContentView之前requestFeature ,在源码有解释,
@Override
public boolean requestFeature(int featureId) {
if (mContentParent != null) {
throw new AndroidRuntimeException("requestFeature() must be called before adding content");
}前面说到 mContentParent 是在setContentView中 被赋值的(id为content的ViewGroup),当在setContentView之后再设置requestFeature的话mContentParent 就不是为空了,那时候就会抛出异常了
















