如果你對 Android 有一定了解的話,你一定知道 View 的樹形結(jié)構(gòu),View 的測量、繪制和事件分發(fā)都是從樹的根部逐級遍歷分發(fā)下去的,而這個樹形結(jié)構(gòu)的根部就是我們今天要講的 DecorView。下面是我畫的一張圍繞 DecorView 的層級關(guān)系圖,其中最頂層是我們熟知的 Activity,每個 Activity 會有一個 Window 對象,該 Window 對象包含的就是 DecorView:

接下來我們就自上而下,從源碼的角度看看 DecorView 到底是什么。首先從我們最熟悉的一句代碼說起,它就是 Activity 的 setContentView(),我們在 Activity 的 onCreate() 回調(diào)里都會調(diào)用該方法將布局文件設(shè)置給 Activity,那么該方法里面做了什么事情呢?來看下面一段源碼:
public class Activity {
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
}
}
很簡單的一句話,就是獲取 Activity 的 Window 對象將布局資源設(shè)置給它,Window 是一個抽象類,它的唯一實(shí)現(xiàn)類是 PhoneWindow,所以接下來我們就去看看 PhoneWindow 的 setContentView() 方法做了什么:
public class PhoneWindow extends Window {
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 初始化 DecorView
installDecor();
} else {
mContentParent.removeAllViews();
}
// 解析我們設(shè)置的布局資源并且設(shè)置 mContentParent 為父布局
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
PhoneWindow 的 setContentView() 方法主要做了兩件事情,初始化 DecorView 然后解析我們設(shè)置的布局資源到指定的父布局 mContentParent 中,那么我們先從 installDecor() 方法入手,看下 DecorView 是怎么初始化的,mContentParent 我們留到最后來講:
public class PhoneWindow extends Window {
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
}
DecorView 的初始化分為兩步,分別是創(chuàng)建 DecorView 和 初始化 DecorView 的布局,其中創(chuàng)建 DecorView 的方法 generateDecor() 很簡單,里面就一句話創(chuàng)建一個新的 DecorView 對象,代碼如下所示:
public class PhoneWindow extends Window {
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
}
你一定想知道 DecorView 是什么東西吧?那么我們就來看看下面的 DecorView 源碼,其實(shí) DecorView 繼承自 FrameLayout,所以它實(shí)際上就是一個 ViewGroup。
private final class DecorView extends FrameLayout {}
知道 DecorView 是一個 ViewGroup 之后,我們繼續(xù)看看它內(nèi)部都裝了什么東西,我們來看 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) {
// 此處省去一堆代碼,設(shè)置窗口屬性
int layoutResource;
// 此處省去一堆代碼,根據(jù)不同的主題使用不同的布局資源
// 這里才是重點(diǎn),向 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");
}
// 此處省去一堆代碼設(shè)置窗口背景和標(biāo)題
return contentParent;
}
}
從上面的源碼可以看出實(shí)際上 DecorView 里面包含了一個系統(tǒng)內(nèi)置的布局資源,這個布局資源 layoutResource 會根據(jù)不同主題變化,其中一個資源是 com.android.internal.R.layout.screen_simple,該資源文件里有一個 ID 為 content 的 FrameLayout,它就是我們前面看到的 mContentParent,我們設(shè)置的布局文件就是被解析并添加到 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 的分析就結(jié)束了,總結(jié)以下幾點(diǎn):
- DecorView 繼承自 FrameLayout,是一個 ViewGroup
- DecorView 是 Window / Activity 的最頂級視圖
- DecorView 是在我們調(diào)用 Activity 的
setContentView()方法時創(chuàng)建的,期間還會獲取并應(yīng)用我們設(shè)置的窗口屬性,所以在setContentView()之前設(shè)置的窗口屬性才能生效