深入理解 DecorView

如果你对 Android 有一定了解的话,你一定知道 View 的树形结构,View 的测量、绘制和事件分发都是从树的根部逐级遍历分发下去的,而这个树形结构的根部就是我们今天要讲的 DecorView。下面是我画的一张围绕 DecorView 的层级关系图,其中最顶层是我们熟知的 Activity,每个 Activity 会有一个 Window 对象,该 Window 对象包含的就是 DecorView:

深入理解 DecorView_第1张图片
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 中的:


    
    
              
    
        

到此为止,我们对 DecorView 的分析就结束了,总结以下几点:

  • DecorView 继承自 FrameLayout,是一个 ViewGroup
  • DecorView 是 Window / Activity 的最顶级视图
  • DecorView 是在我们调用 Activity 的 setContentView() 方法时创建的,期间还会获取并应用我们设置的窗口属性,所以在 setContentView() 之前设置的窗口属性才能生效

你可能感兴趣的:(深入理解 DecorView)