×

View —DecorView 的层级

96
SharryChoo
2018.01.13 00:10* 字数 417

转载请说明出处 https://www.jianshu.com/p/3872219cc07a
我们经常在Activity的onCreate中的调用setCotnentView(layoutRes)方法, 这里我们从Activity入手去看看它具体做了哪些操作

从 onCreate 中的 setContentView 开始分析

    /**
     * Activity.setContentView
     */
    public void setContentView(@LayoutRes int layoutResID) {
        // 很显然getWindow拿到的是PhoneWidow的实例, 我们直接进去看
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

    /**
     * PhoneWindow.setContentView
     */
    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            // 1. (重点)根据 mContentParent 是否为空来判断是否需要调用 installDecor() 方法
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 2. (非重点) 这里将我们的布局文件 inflate 到 mContentParent 中
            // 所以我们必须搞清楚 mContentParent 是什么
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

好, setContentView 这个方法主要做了三件事情

  1. 根据 mContentParent 是否为空来判断是否需要调用 installDecor() 方法
  2. 将 layoutResID inflate 进 mContentParent 中

这里我们主要关注 installDecor 做了哪些事情

    /** 
     * PhoneWindow.installDecor
     * 省略了大量无关代码
     */
    private void installDecor() {
        if (mDecor == null) {
            // 1. 创建了 DecorView 的对象
            mDecor = generateDecor(-1); // new DecorView(context, featureId, this, getAttributes())
        } else {
            // 2. 若不为null, 则直接与当前的 Window 对象绑定
            mDecor.setWindow(this);
        }
        // 3. 构建 mContentParent
        if (mContentParent == null) {
            // generateLayout方法比较重要, 接下来我们分析这个方法
            // 看看它是如何构造我们的mContentParent的
            mContentParent = generateLayout(mDecor);
        }
    }
    
     /** 
     * PhoneWindow.generateLayout
     * 省略了大量无关代码
     */
    protected ViewGroup generateLayout(DecorView decor) {
        ......// 这里省略一堆代码
        
        int layoutResource;
        int features = getLocalFeatures();
        
        ......// 3.1 这里的代码是根据 fetaures 给 layoutResource 赋值
        
        // mDecor要改变的标记位
        mDecor.startChanging();
        // 3.2 这个方法比较有价值, 它将 layoutResource 布局文件解析成 View 添加到了 DecorView 之中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        // 3.3 通过findViewById给contentParent赋值
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        
        ......// 这里省略一堆代码
        
        // mDecor改变结束的标记位
        mDecor.finishChanging();
        return contentParent;
    }

3.1看看其中几个 features 所对应的 layoutResource 的布局文件的样式

//screen_simple.xml
<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"
              android:theme="?attr/actionBarTheme" />
    <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>

// screen_simple_overlay_action_mode.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <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" />
    <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>


无一例外, 无论看几个都有一个id为content 的 FrameLayout

3.2 看看 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource) 做了什么

  /**
   * DecorView.onResourcesLoaded
   */
   void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        // Decor的标题View, 这个类表示用于控制自由格式窗口的特殊屏幕元素环境
        mDecorCaptionView = createDecorCaptionView(inflater);
        // 1 通过 LayoutInflate 来创建这个layoutResource 的 View 实例对象 root
        final View root = inflater.inflate(layoutResource, null);
        // 2 若不 null 则先添加 mDecorCaptionView, 再向 mDecorCaptionView 中添加 root
        if (mDecorCaptionView != null) {
            // 将 mDecorCaptionView 添加进 DecorView 
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            // 将从系统的资源文件获取的 root 加到这个 mDecorCaptionView 中
            mDecorCaptionView.addView(root,new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {
            // 3 若 mDecorCaptionView 为 null, 则直接将 root 加到 DecorView 中
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        // 4. 强转成 ViewGroup, 传递给 mContentRoot
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

   /**
    * DecorView.inflateDecorCaptionView
    */
   private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater){
        final Context context = getContext();
        // We make a copy of the inflater, so it has the right context associated with it.
        inflater = inflater.from(context);
        final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption,
                null);
        setDecorCaptionShade(context, view);
        return view;
    }
    
<com.android.internal.widget.DecorCaptionView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:descendantFocusability="beforeDescendants" >
    <LinearLayout
            android:id="@+id/caption"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="end"
            android:background="@drawable/decor_caption_title"
            android:focusable="false"
            android:descendantFocusability="blocksDescendants" >
        <Button
                android:id="@+id/maximize_window"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:layout_margin="5dp"
                android:padding="4dp"
                android:layout_gravity="center_vertical|end"
                android:contentDescription="@string/maximize_button_text"
                android:background="@drawable/decor_maximize_button_dark" />
        <Button
                android:id="@+id/close_window"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:layout_margin="5dp"
                android:padding="4dp"
                android:layout_gravity="center_vertical|end"
                android:contentDescription="@string/close_button_text"
                android:background="@drawable/decor_close_button_dark" />
    </LinearLayout>
</com.android.internal.widget.DecorCaptionView>

可以看到 mDecor.onResourcesLoaded 主要做了三件事情

  1. 通过 LayoutInflate 来创建这个layoutResource 的 View 实例对象 root
  2. 若不 null 则先添加 mDecorCaptionView, 再向 mDecorCaptionView 中添加 root
  3. 若 mDecorCaptionView 为 null, 则直接将 root 加到 DecorView 中
  4. 将加载系统资源文件创建的 View 的实例赋给 mContentRoot

==通过上面的源码可以知道, 系统创建的 DecorView 中只会存在一个子 View==

  • mDecorCaptionView(它内部也包含了mContentRoot)
  • mContentRoot

3.3 最终的 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

/**
 * Window.ID_ANDROID_CONTENT
 * 这个 ID_ANDROID_CONTENT 即 mDecor 中的 mContentRoot 中的 FrameLayout 的 Id
 * 也就是我们 setContentView 的地方 layoutRes 最终加载进去的地方
 */
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    

表格

容器 childCount child
Window 1 DecorView
DecorView(只有一个子孩子, 两种可能) 1 may1(一般都是这个): mContentRoot
(根据feature拿到的layoutResource 例如: screen_simple.xml)
may2: DecorCaptionView
DecorCaptionView(作为了解) 2 child1: LinearLayout(@+id/caption)
child2: mContentRoot
(根据feature拿到的layoutResource 例如: screen_simple.xml)
mContentRoot(以screen_simple.xml为例) 2 child1: ViewStub(@+id/action_mode_bar_stub)
child2: FrameLayout(@android:id/content), 看到这个ID就放心了, 它就是mCotnentParent

结论

  1. 这里我们定位到mContentRoot的child2: FrameLayout(@android:id/content)
  2. FrameLayout(@android:id/content)与Window.ID_ANDROID_CONTENT一致
  3. FrameLayout(@android:id/content)即我们的上面源码中看到的mContentParent
  4. 注意:
    • 如果继承了AppCompatActivity的话, 它会对这个FrameLayout(@android:id/content)进行处理, 会在其内部添加两个子View分别为ViewStubCompat和ContentFrameLayout
    • 这个ContentFrameLayout将会代替我们的FrameLayout(@android:id/content)成为我们setContentView()中设置的layoutRes的直接父容器

层级图

DecorView层级图.jpg
AndroidSourceParse
Web note ad 1