如果你对 Android 有一定了解的话,你一定知道 View 的树形结构,View 的测量、绘制和事件分发都是从树的根部逐级遍历分发下去的,而这个树形结构的根部就是我们今天要讲的 DecorView。下面是我画的一张围绕 DecorView 的层级关系图,其中最顶层是我们熟知的 Activity,每个 Activity 会有一个 Window 对象,该 Window 对象包含的就是 DecorView:
接下来我们就自上而下,从源码的角度看看 DecorView 到底是什么。首先从我们最熟悉的一句代码说起,它就是 Activity 的 setContentView()
,我们在 Activity 的 onCreate()
回调里都会调用该方法将布局文件设置给 Activity,那么该方法里面做了什么事情呢?来看下面一段源码:
public class Activity {
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
}
}
很简单的一句话,就是获取 Activity 的 Window 对象将布局资源设置给它,Window 是一个抽象类,它的唯一实现类是 PhoneWindow,所以接下来我们就去看看 PhoneWindow 的 setContentView()
方法做了什么:
public class PhoneWindow extends Window {
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 初始化 DecorView
installDecor();
} else {
mContentParent.removeAllViews();
}
// 解析我们设置的布局资源并且设置 mContentParent 为父布局
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
PhoneWindow 的 setContentView()
方法主要做了两件事情,初始化 DecorView 然后解析我们设置的布局资源到指定的父布局 mContentParent 中,那么我们先从 installDecor()
方法入手,看下 DecorView 是怎么初始化的,mContentParent 我们留到最后来讲:
public class PhoneWindow extends Window {
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
}
DecorView 的初始化分为两步,分别是创建 DecorView 和 初始化 DecorView 的布局,其中创建 DecorView 的方法 generateDecor()
很简单,里面就一句话创建一个新的 DecorView 对象,代码如下所示:
public class PhoneWindow extends Window {
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
}
你一定想知道 DecorView 是什么东西吧?那么我们就来看看下面的 DecorView 源码,其实 DecorView 继承自 FrameLayout,所以它实际上就是一个 ViewGroup。
private final class DecorView extends FrameLayout {}
知道 DecorView 是一个 ViewGroup 之后,我们继续看看它内部都装了什么东西,我们来看 generateLayout()
方法:
public abstract class Window {
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
@Nullable
public View findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
}
public class PhoneWindow extends Window implements MenuBuilder.Callback {
protected ViewGroup generateLayout(DecorView decor) {
// 此处省去一堆代码,设置窗口属性
int layoutResource;
// 此处省去一堆代码,根据不同的主题使用不同的布局资源
// 这里才是重点,向 DecorView 添加布局,并且从 DecorView 中查找出 contentParent
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
// 此处省去一堆代码设置窗口背景和标题
return contentParent;
}
}
从上面的源码可以看出实际上 DecorView 里面包含了一个系统内置的布局资源,这个布局资源 layoutResource
会根据不同主题变化,其中一个资源是 com.android.internal.R.layout.screen_simple
,该资源文件里有一个 ID 为 content 的 FrameLayout,它就是我们前面看到的 mContentParent,我们设置的布局文件就是被解析并添加到 mContentParent 中的:
<LinearLayout xmlns:android="http://schemas.android.com/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" />
<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>
到此为止,我们对 DecorView 的分析就结束了,总结以下几点:
- DecorView 继承自 FrameLayout,是一个 ViewGroup
- DecorView 是 Window / Activity 的最顶级视图
- DecorView 是在我们调用 Activity 的
setContentView()
方法时创建的,期间还会获取并应用我们设置的窗口属性,所以在setContentView()
之前设置的窗口属性才能生效