前语

信任 Android 开发都知道,View 是树形结构,一组 View 的集合便是 ViewGroup,而 ViewGroup 中又能够包括 View 和其他 ViewGroup,然后构成了树结构。那么问题来了,这棵树的根又是什么呢?接下来就让咱们一起来探求一下 Android 的顶级 View——DecorView

1. 从 Activity 探求 View 的布局

之所以从 Activity 来开端看,是因为一个 App 的界面都是由 Activity 加载各种各样的布局得到的,这儿的布局当然便是由 View 组成的啦,Activity 加载布局的代码信任大家都很了解:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.zmq_test);
}

进去 setContentView 的源码来康康它终究做了些什么:

public void setContentView(int layoutResID) {
    // 1.getWindow()
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

先看看注释 1 处,咱们看看 getWindow 是什么东东:

public Window getWindow() {
    return mWindow;
}

getWindow 直接回来了一个私有变量 mWindow,那咱们就全局找一下 mWindow 是在哪里被初始化的吧?

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
        IBinder shareableActivityToken) {
    attachBaseContext(context);
    mFragments.attachHost(null);
    // 初始化 mWindow
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    ...
}

咱们在 Activityattch 办法中找到了 mWindow 初始化的踪迹。本来 mWindowPhoneWindow 类,承继自 Window,既然知道了 getWindow 回来的其实是 PhoneWindow,那就去看看 PhoneWindowsetContentView 办法都做了些什么吧:

public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        // 1.初始化
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    ...
}

这段代码很明晰,假如放置内容的视图为空,就需要去履行 installDecor 办法:

private void installDecor() {
    if (mDecor == null) {
        // 1. 初始化 decor
        mDecor = generateDecor(-1);
        ...
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        // 2. 生成布局
        mContentParent = generateLayout(mDecor);
        ...
    }
}

installDecor 办法的代码太长啦,咱们挑要点部分来看一看,这儿现已能看到 DecorView 相关的蛛丝马迹了,mDecor 为空的时候就去生成一个目标,这个目标是不是便是咱们要寻觅的 DecorView 呢?

protected DecorView generateDecor(int featureId) {
    ...
    return new DecorView(context, featureId, this, getAttributes());
}

公然!在 generateDecor 办法中找到了创立 DecorView 的代码,DecorView 承继自 FrameLayout,也便是 View 的子类,更是 Activity 中的根 View。那么 Activity 到底是如何把 DecorView 作为根 View 布局的呢?咱们接着往下看,注释 2 处的 generateLayout 运用创立的 mDecor 做了什么吧:

protected ViewGroup generateLayout(DecorView decor) {
    // 1.依据当时的 Activity 主题来设置一些特点.
    TypedArray a = getWindowStyle();
    ...
    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
        requestFeature(FEATURE_NO_TITLE);
    } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
        // Don't allow an action bar if there is no title.
        requestFeature(FEATURE_ACTION_BAR);
    }
    ...
    // 2.给 layoutResource 赋值不同的资源 id ,加载不同的布局。
    int layoutResource;
    int features = getLocalFeatures();
    if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
            && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        layoutResource = R.layout.screen_progress;
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogCustomTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_custom_title;
        }
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
            layoutResource = a.getResourceId(
                    R.styleable.Window_windowActionBarFullscreenDecorLayout,
                    R.layout.screen_action_bar);
        } else {
            layoutResource = R.layout.screen_title;
        }
    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        layoutResource = R.layout.screen_simple;
    }
    mDecor.startChanging();
    // 3.加载资源
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    ...
    return contentParent;
}

generateLayout 办法中的代码有足足几百行,其实从办法姓名也能看出来,这个办法是用来生成布局的。首先会依据开发设置的一些主题来调整咱们的布局,然后便是给咱们的窗口 Window 进行装饰啦。能够看到,依据不同的状况会运用不同的布局。最终在注释 3 处,mDecor 目标会依据选定的 layoutResource 来加载布局,咱们随意点一个 layout 进去康康布局长什么样?

<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"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout android:id="@android:id/title_container" 
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        android:transitionName="android:title"
        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>

我这儿进入的是 R.layout.screen_custom_title,能够看到布局中供给了一个 ActionBar,两个 FrameLayout 分别用来显现 titlecontent 部分的。实际上咱们在开发时所写的布局便是展示在content 中的,一般咱们不会运用 ActionBar 或者 title,而是在使用中自己去完成标题。这也是为什么 Activity 中叫 setContentView,因为咱们操作的是 content 部分。

咱们能够用一张图来表明 ActivityDecorView 之间的联系:

View 系列 —— 小白也能看懂的 DecorView

2. 总结

依据上面的剖析,信任你现已理解了 DecorView 作为根 View 是如何被创立以及加载的,为了更明晰简单用流程图表达下:

View 系列 —— 小白也能看懂的 DecorView
  1. Activityattch 时,会创立 PhoneWindow 目标,在 onCreate 履行其 setContentView 办法;
  2. setContentView 中会运用 installDecor 来创立一个 DecorView 目标作为根 View
  3. 得到了 DecorView 目标后,会经过 generateLayout 办法获取对应的资源 id,DecorView 会依据该 id 来加载不同的布局;
  4. DecorView 作为一个顶级 View,一般状况下内部会包括一个 LinearLayout,并将屏幕划分红 TitleViewContentView 两部分。