findViewById(android.R.id.content)
,很眼熟;setContentView
,天天用。从这两个方向深究下去都会见到Window、PhoneWindow和DecorView。
想知道是什么承载了xml里的布局,想知道查看手机布局最外层总看到的FrameLayout是从哪来的,一切都在这里。
Window
位于 /frameworks/base/core/java/android/view/Window.java。该类是一个抽象类,提供了绘制窗口的一组通用API。可以将之理解为一个载体,各种View在这个载体上显示。PhoneWindow
位于/frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindow.java。该类继承于Window类,是Window类的具体实现,即我们可以通过该类具体去绘制窗口。并且,该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。 简而言之,PhoneWindow类是把一个FrameLayout类即DecorView对象进行一定的包装,将它作为应用窗口的根View,并提供一组通用的窗口操作接口。DecorView
是PhoneWindow类的内部类。该类是一个FrameLayout的子类,并且是PhoneWindow的子类,该类就是对普通的FrameLayout进行功能的扩展,更确切点可以说是修饰(Decor的英文全称是Decoration,即“修饰”的意思),比如说添加TitleBar(标题栏),以及TitleBar上的滚动条等 。最重要的一点是,它是所有应用窗口的根View 。
Window类相当于一幅画(抽象概念,什么画我们未知) ,PhoneWindow为一副齐白石先生的山水画(具体概念,我们知道了是谁的、什么性质的画),DecorView则为该山水画的具体内容(有山、有水、有树,各种界面)。DecorView呈现在PhoneWindow上。
当系统(一般是ActivityManagerService)配置好启动一个Activity的相关参数(包括Activity对象和Window对象信息)后,就会回调Activity的onCreate()方法,在其中我们通过设置setContentView()方法类设置该Activity的显示界面,整个调用链由此铺垫开来。setContentView()的三个构造方法调用流程本质上是一样的,我们就分析setContentView(intresId)方法。
Activity.setContentView(intresId) 该方法在Activity类中,该方法只是简单的回调Window对象,具体为PhoneWindow对象的setContentView()方法实现。
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initActionBar();
}
public Window getWindow() {
return mWindow; //Window对象,本质上是一个PhoneWindow对象
}
PhoneWindow.setContentView() 该方法在PhoneWindow类中
@Override
public void setContentView(int layoutResID) {
//是否是第一次调用setContentView方法, 如果是第一次调用,则mDecor和mContentParent对象都为空
if (mContentParent == null) { //首先判断mContentParent是否为null,是则调用installDecor()
installDecor();
} else { //否则移除其内部所有的子Views
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent); //然后通过LayoutInflater.inflate将我们传入的layout放置到mContentParent中
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
}
从这里就能看出mContentParent是个ViewGroup且包裹我们整个布局文件;而installDecor()就是去初始化我们这个mContentParent
接下来来看看DecorView,它是PhoneWindow的内部类:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
这时你看到了最外层总看到的FrameLayout是从哪来的了
然后看一下findViewById的代码:
public View findViewById(int id) {
return getDecorView().findViewById(id);
}
DecorView算是轴心。
DecorView是一个FrameLayout,然后会根据theme去选择系统中的布局文件,将布局文件通过inflate转化为view,加入到DecorView中;
这些布局文件中都包含一个id为content的FrameLayout,将其引用返回给mContentParent。有了mContentParent,然后把我们写的布局文件通过inflater加入到mContentParent中。
关于android.R.id.content也可以直观的看到。
比如R.layout.xxx可以在frameworks\base\core\res\res\layout里面进行查看,比如R.layout.screen_custom_title.xml:
<?xml version="1.0" encoding="utf-8"?>
<!--
This is a custom layout for a screen.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<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" />
<FrameLayout android:id="@android:id/title_container"
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
上面的title_container是用来放自定义Title的容器,而下面的content就是放置我们设置的布局的容器(android.R.id.content)。
首先初始化mDecor,即DecorView为FrameLayout的子类。就是我们整个窗口的根视图了。
然后,根据theme中的属性值,选择合适的布局,通过infalter.inflater放入到我们的mDecor中。
在这些布局中,一般会包含ActionBar,Title,和一个id为content的FrameLayout。
最后,我们在Activity中设置的布局,会通过infalter.inflater压入到我们的id为android.R.id.content的FrameLayout中去(hierarchy看到两层FrameLayout的原因)
参考:
Android 源码解析 之 setContentView
补充说明Window、PhoneWindow与DecorView