1【Android 11】View层级结构

在这里插入图片描述

1 窗口

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window

翻译:顶级窗口外观和行为策略的抽象基类。 此类的实例应用作添加到window manager的顶级视图。 它提供了标准的 UI 策略,例如背景、标题区域、默认按键事件处理等。这个抽象类的唯一现有实现是android.view.PhoneWindow,当需要一个Window时你应该实例化它。

Window类不仅是个抽象类,而且它的定义也挺抽象,但是从这个注释中,我们还是可以得到两点和我们今天所分析的内容相关的信息:

1)、Window在View的层级结构中是作为顶级视图存在的:

2)、Window会被添加到WindowManager,由WindowManager管理。

那么我们了解窗口,也是从这两个角度去出发:

1)、微观角度,探究窗口内部的构造。

2)、宏观角度,探究WMS如何管理屏幕上显示的诸多窗口。

至于窗口,我们先理解它为一块在屏幕上可以显示图像的区域。

2 微观下的Window —— View层级结构

从Activity的层面讲,由于Activity是通过各种UI元素与用户进行交互的,那么这些UI元素就需要一个统一的载体来承载它们,这个UI元素的容器也就是Window。

对于每一个Activity,或者每一个窗口来说,它的界面布局都是由多层View嵌套合成得到的,如果把View的层级结构比作一个树状图,那么Window应该是根节点root view的存在,我们自定义的一些布局xml文件中的一些layout就是中间节点,子节点就是这些layout中定义的如Button、TextView等很具体的View。

在这里插入图片描述

如果我们想要再深入一点去探究Window的View层级结构的话,会发现上面的说法其实是不够严谨的。View的层级结构是由各种View层层嵌套得来的,这并不是一个抽象的说法,而是实实在在的由一个ViewGroup嵌套多个子ViewGroup,多个子ViewGroup再分别嵌套多个子子ViewGroup,这样一层层嵌套得来的。

ViewGroup是一种特殊的View,它可以包含其他的View,我们通常所用的FrameLayout、RelativeLayout和LinearLayout等,都继承自ViewGroup,因此ViewGroup可以视作为View的容器类,它有一个View数组类型的成员变量mChildren来存放它的子View:

    // Child views of this ViewGroup
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    private View[] mChildren;

并且ViewGroup本身也是继承View的:

public abstract class ViewGroup extends View implements ViewParent, ViewManager

那么ViewGroup就可以作为一个View添加到某一个ViewGroup的mChildren数组中,这就让一个父ViewGroup包含多个子ViewGroup成为可能,就像这样:

在这里插入图片描述

我个人习惯用树状图表示:

在这里插入图片描述

既然ViewGroup的为View的多级嵌套提供了支持,那么在View的层级结构中,最顶层View应该也是一个ViewGroup。而Window的唯一实现类,PhoneWindow,只继承了Window,因此PhoneWindow是无法作为当前Window的View的层级结构的根节点的,因为根节点应该是一个ViewGroup类型的类。

那么根节点对应的是哪个类?这里我们自问自答,View的层级结构的根节点是DecorView,一个真正的View:

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks

一般在网上百度Activity、Window和DecorView之间的关系,都会得到类似下面的图:

在这里插入图片描述

之前我以为Activity的区域对应显示设备的整个区域,Activity的区域包含PhoneWindow区域,PhoneWindow的区域又包含DecorView的区域。。。但是实际上,Activity和PhoneWindow都是一个抽象的概念,都没有一个具体的显示区域,而DecorView,作为一个View,它的作用正如它作为PhoneWindow的成员变量注释中所描述的:

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

DecorView是一个窗口的最顶层View,因此真正承载UI元素是DecorView,毕竟View才是我们实打实可以看的到的UI元素。

总结一下,上图可以分为抽象和具象两部分:

1)、一方面,Activity启动的时候会创建一个PhoneWindow对象,将UI处理交给PhoneWindow;而PhoneWindow在后续Activity加载布局资源的时候生成一个DecorView,所以在抽象概念上,是Activity > PhoneWindow > DecorView的,如下图所示:

在这里插入图片描述

2)、另一方面,由于Activity和PhoneWindow都是抽象的概念,我们直接看到的其实是DecorView。并且DecorView在生成View层级结构的时候,是作为根View的,所以在具象概念上,或者说我们能够看到的层面上,DecorView是最外层的,如下图所示:

在这里插入图片描述

3 View层级结构的生成

那么我们想要了解一个Window的内部构造,就需要知道以DecorView为根View的View层级结构是怎么生成的。

我们经常在Activity#onCreate中调用Activity#setContentView去加载指定的布局文件,就像这样:

setContentView(R.layout.activity_main);

DecorView就是在这个过程中创建的。

3.1 Activity#setContentView

    /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

这里Activity#getWindow返回的便是和当前Activity一一对应的Window对象。

3.2 PhoneWindow#installDecor

本来我们看到上一步,应该是调用到PhoneWindow#setContentView的,但是我们写的Activity是继承了AppCompatActivity的,AppCompatActivity又重写了Activity#setContentVIew方法,所以流程就有了些许不一样,先走到的是PhoneWindow#installDecor,PhoneWindow#setContentView要在稍晚的时间点才会被调用:

调用堆栈是:

08-20 09:38:31.520  6254  6254 I ukynho_test: MainActivity#onCreate
08-20 09:38:31.543  6254  6254 I ukynho_test: call setContentView
08-20 09:38:31.548  6254  6254 I ukynho_decor: PhoneWindow#installDecor ---- title = null    mDecor = null   mContentParent = null
08-20 09:38:31.548  6254  6254 I ukynho_decor: java.lang.Throwable
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at com.android.internal.policy.PhoneWindow.installDecor(PhoneWindow.java:2716)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at com.android.internal.policy.PhoneWindow.getDecorView(PhoneWindow.java:2128)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:717)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:659)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:552)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:161)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at com.qq.reader.activity.MainActivity.onCreate(MainActivity.java:29)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at android.app.Activity.performCreate(Activity.java:8012)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at android.app.Activity.performCreate(Activity.java:7996)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1311)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3525)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3713)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2156)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at android.os.Handler.dispatchMessage(Handler.java:106)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at android.os.Looper.loop(Looper.java:236)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at android.app.ActivityThread.main(ActivityThread.java:7814)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at java.lang.reflect.Method.invoke(Native Method)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:621)
08-20 09:38:31.548  6254  6254 I ukynho_decor:  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:997)

PhoneWindow#installDecor方法内容如下:

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeFrameworkOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            // ......
        }
    }

这个方法中和我们现在分析的内容相关的有两点:

1)、通过PhoneWindow#generateDecor生成DecorView。

2)、通过PhoneWindow#generateLayout生成Decor的View层级结构。

3.2.1 DecorView的生成

        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }

在PhoneWIndow#installDecor中,由于我们是首次创建,所以DecorView类型的mDecor是空的,需要调用PhoneWIndow#generateDecor去生成一个DecorView。

    protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, this);
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }

mUseDecorContext在PhoneWindow构造方法中被设置为true,那么这里会创建一个DecorView专用的上下文DecorContext,然后DecorView基于这个新建的DecorContext创建。

3.2.2 DecorView层级结构的生成

这里我们拿一个测试App做例子:

在这里插入图片描述

一个带了ActionBar和三个按钮的Activity。

我这边查看Activity布局信息一般是通过两种方式,一是adb shell dumpsys activity top,这里可以看下dumpsys出来的信息:

    View Hierarchy:
      DecorView@7b3553[MainActivity]
        android.widget.LinearLayout{fe72e90 V.E...... ........ 0,0-1200,1904}
          android.view.ViewStub{8fee389 G.E...... ......I. 0,0-0,0 #10201c3 android:id/action_mode_bar_stub}
          android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904}
            androidx.appcompat.widget.ActionBarOverlayLayout{7f9d3af V.E...... ........ 0,0-1200,1856 #7f07004d app:id/decor_content_parent}
              androidx.appcompat.widget.ContentFrameLayout{c38a3bc V.E...... ........ 0,128-1200,1856 #1020002 android:id/content}
                android.widget.RelativeLayout{5692f45 V.E...... ........ 0,0-1200,1728}
                  androidx.appcompat.widget.AppCompatButton{e8e569a VFED..C.. ........ 200,200-376,296 #7f070059 app:id/getSize}
                  androidx.appcompat.widget.AppCompatButton{619fbcb VFED..C.. ........ 0,296-243,392 #7f07009c app:id/startAnother}
                  androidx.appcompat.widget.AppCompatButton{dca3fa8 VFED..C.. ........ 0,392-220,488 #7f070058 app:id/getLocation}
              androidx.appcompat.widget.ActionBarContainer{2727ac1 V.ED..... ........ 0,0-1200,128 #7f070029 app:id/action_bar_container}
                androidx.appcompat.widget.Toolbar{e6b4266 V.E...... ........ 0,0-1200,128 #7f070027 app:id/action_bar}
                  androidx.appcompat.widget.AppCompatTextView{2ca89a7 V.ED..... ........ 48,37-316,91}
                  androidx.appcompat.widget.ActionMenuView{457ae54 V.E...... ........ 1184,0-1184,128}
                androidx.appcompat.widget.ActionBarContextView{96b01fd G.E...... ......I. 0,0-0,0 #7f07002f app:id/action_context_bar}
        android.view.View{3d91ef2 V.ED..... ........ 0,1904-1200,2000 #1020030 android:id/navigationBarBackground}
        android.view.View{c0c1943 V.ED..... ........ 0,0-1200,48 #102002f android:id/statusBarBackground}

另一种就是借助SDK自带的工具hierarchyviewer:

在这里插入图片描述

这个工具可以看到View的一些具体信息,如寬高,坐标,透明度等,分析问题的时候很有用。

通过这两种方式,看到最顶层是DecorView这不用多说,但是自DecorView到Activity自定义的布局结构,中间还有很多层View,这些便是在PhoneWindow#generateLayout中生成的,也就是PhoneWindow#installDecor的第二部分:

        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ... ...
        }

在这一步之前我们只是生成了DecorView这一层,看下这一步是如何生成其他View的。

    protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        TypedArray a = getWindowStyle();

        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }

        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
            getAttributes().setFitInsetsSides(0);
            getAttributes().setFitInsetsTypes(0);
        }

        // ......

        if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
                false)) {
            setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
                    & (~getForcedWindowFlags()));
        }

        if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
                false)) {
            setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
                    & (~getForcedWindowFlags()));
        }


        // ......
        
        if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {
            requestFeature(FEATURE_CONTENT_TRANSITIONS);
        }
        if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {
            requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
        }

        mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);

        final Context context = getContext();
        final int targetSdk = context.getApplicationInfo().targetSdkVersion;
        final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
        final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;

        if (!mForcedStatusBarColor) {
            mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
        }
        if (!mForcedNavigationBarColor) {
            mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
            mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
                    0x00000000);
        }
        if (!targetPreQ) {
            mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
                    R.styleable.Window_enforceStatusBarContrast, false);
            mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
                    R.styleable.Window_enforceNavigationBarContrast, true);
        }

        WindowManager.LayoutParams params = getAttributes();

        // Non-floating windows on high end devices must put up decor beneath the system bars and
        // therefore must know about visibility changes of those.
        if (!mIsFloating) {
            if (!targetPreL && a.getBoolean(
                    R.styleable.Window_windowDrawsSystemBarBackgrounds,
                    false)) {
                setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                        FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
            }
            if (mDecor.mForceWindowDrawsBarBackgrounds) {
                params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
            }
        }
        if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
            decor.setSystemUiVisibility(
                    decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        }
        if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
            decor.setSystemUiVisibility(
                    decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
        }

        // ......

        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        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;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            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;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        // ......

        mDecor.finishChanging();

        return contentParent;
    }

虽然方法很长,但是其实很简单,主要是读取Activity自己设置的窗口风格属性,然后进行一些配置,比较重要的部分有:

1、设置Window flags,如:

        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
        }

2、启用Screen features,如:

        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);
        }

3、设置StatusBar和NavigationBar的背景色,如:

        if (!mForcedStatusBarColor) {
            mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
        }
        if (!mForcedNavigationBarColor) {
            mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
            mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
                    0x00000000);
        }

4、设置SystemUI flags,如:

        if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
            decor.setSystemUiVisibility(
                    decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        }
        if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
            decor.setSystemUiVisibility(
                    decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
        }

5、设置PhoneWindow的一些变量,如:

        mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
        // ... ...
        if (!targetPreQ) {
            mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
                    R.styleable.Window_enforceStatusBarContrast, false);
            mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
                    R.styleable.Window_enforceNavigationBarContrast, true);
        }

6、选取应该加载的布局资源ID:

        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        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;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            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;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

这里根据Activity的主题设置取得最符合的布局文件ID后,调用DecorView#onResourcesLoaded去实例化一个相符合的View层次结构:

    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        if (mBackdropFrameRenderer != null) {
            loadBackgroundDrawablesIfNeeded();
            mBackdropFrameRenderer.onResourcesLoaded(
                    this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                    mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                    getCurrentColor(mNavigationColorViewState));
        }

        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                        new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {
            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

这里不去考虑DecorCaptionView存在的情况,那么这里的主要工作就是:

  • 调用layoutInflater.inflate去生成一个View层次结构,将DecorView.mContentRoot指向该View层级结构的rootView,并返回该root View:
final View root = inflater.inflate(layoutResource, null);
  • 通过View#addView将这个root View加入到当前DecorView的层次结构中,也就是说,该root View变成了DecorView的子VIew,在Decor的层级结构中变成了DecorView的子节点:
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

这里我本地调试,取到的布局资源ID为:R.layout.screen_simple,对应的xml是:frameworks/base/core/res/res/layout/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>

布局文件本身是一个LinearLayout,包含一个ViewStub和FrameLayout,再参考dumpsys出来的信息,是对上了的:

      DecorView@7b3553[MainActivity]
        android.widget.LinearLayout{fe72e90 V.E...... ........ 0,0-1200,1904}
          android.view.ViewStub{8fee389 G.E...... ......I. 0,0-0,0 #10201c3 android:id/action_mode_bar_stub}
          android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904}

在这里插入图片描述

7、找到ID_ANDROID_CONTENT对应的View,并且返回。

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        // ... ...

        mDecor.finishChanging();

        return contentParent;

因为在第6步,我们已经得到了一个基于R.layout.screen_simple生成的View层次结构,那么就可以通过findViewById找到一个ID为ID_ANDROID_CONTENT的子View,然后返回该子View。

    /**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

那么最终,通过PhoneWindow#generateLayout生成了一个View层次结构,并且找到其ID为R.id.content的子View并且返回该子View,然后赋值给了PhoneWindow.mContentParent,这里看到返回的是一个FrameLayout:

android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904}

最后将PhoneWindow.mContentParent指向这个返回的ViewGroup对象。

3.2.3 PhoneWindow#setContentView

之前分析PhoneWindow#installDecor的时候,说了AppCompatActivity由于重写了Activity#setContentView方法,导致先走了上面分析的PhoneWindow#installDecor方法,PhoneWindow#setContentView会在稍晚的一个时间点被调用。

先看下调用堆栈信息:

08-20 09:38:31.734  6254  6254 I ukynho_decor: PhoneWindow#setContentView ---- title = null  mContentParent = android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904}     view = androidx.appcompat.widget.ActionBarOverlayLayout{7f9d3af V.E...... ........ 0,0-1200,1856 #7f07004d app:id/decor_content_parent}
08-20 09:38:31.734  6254  6254 I ukynho_decor: java.lang.Throwable
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at com.android.internal.policy.PhoneWindow.setContentView(PhoneWindow.java:476)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at com.android.internal.policy.PhoneWindow.setContentView(PhoneWindow.java:471)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:855)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:659)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:552)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:161)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at com.qq.reader.activity.MainActivity.onCreate(MainActivity.java:29)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at android.app.Activity.performCreate(Activity.java:8012)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at android.app.Activity.performCreate(Activity.java:7996)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1311)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3525)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3713)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2156)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at android.os.Handler.dispatchMessage(Handler.java:106)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at android.os.Looper.loop(Looper.java:236)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at android.app.ActivityThread.main(ActivityThread.java:7814)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at java.lang.reflect.Method.invoke(Native Method)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:621)
08-20 09:38:31.734  6254  6254 I ukynho_decor:  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:997)

再看下方法的具体内容:

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

到达这里时,通过打印的log,可以知道,此时:

1)、我们前面的分析PhoneWindow#generateLayout返回了一个ViewGroup对象:

android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904}

并且PhoneWindow将其成员变量mContentParent指向该对象。

再看到我们加在这边的log,结果是一致的:

mContentParent = android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904},

2)、根据log,此时传入的View是AppCompatActivity那边已经加载好的View层次结构的root View:

view = androidx.appcompat.widget.ActionBarOverlayLayout{7f9d3af V.E...... ........ 0,0-1200,1856 #7f07004d app:id/decor_content_parent}

然后通过:

mContentParent.addView(view, params);

将传入的View作为子View加入的mContentParent的层级结构中,从而将DecorView与AppCompatActivity部分连接起来:

      DecorView@7b3553[MainActivity]
        android.widget.LinearLayout{fe72e90 V.E...... ........ 0,0-1200,1904}
          android.view.ViewStub{8fee389 G.E...... ......I. 0,0-0,0 #10201c3 android:id/action_mode_bar_stub}
          android.widget.FrameLayout{2d0cf8e V.E...... ........ 0,48-1200,1904}
            androidx.appcompat.widget.ActionBarOverlayLayout{7f9d3af V.E...... ........ 0,0-1200,1856 #7f07004d app:id/decor_content_parent}

在这里插入图片描述

3)、后续AppCompatActivity部分再调用LayoutInflater.inflate,解析我们自定义的layout布局文件R.layout.activity_main,生成我们定义的View层次结构:

android.widget.RelativeLayout{5692f45 V.E...... ........ 0,0-1200,1728}
	androidx.appcompat.widget.AppCompatButton{e8e569a VFED..C.. ........ 200,200-376,296 #7f070059 app:id/getSize}
	androidx.appcompat.widget.AppCompatButton{619fbcb VFED..C.. ........ 0,296-243,392 #7f07009c app:id/startAnother}
	androidx.appcompat.widget.AppCompatButton{dca3fa8 VFED..C.. ........ 0,392-220,488 #7f070058 app:id/getLocation}

在这里插入图片描述

并且AppCompatActivity部分在调用LayoutInflater.inflate的时候,指定了parent View,从而将我们自定义的View层次结构添加到AppCompatActivity自己的View层级结构中,指定的parent View为:

androidx.appcompat.widget.ContentFrameLayout{c38a3bc V.E...... ........ 0,128-1200,1856 #1020002 android:id/content}

从而将AppCompatActivity和我们的自定义布局RelativeLayout连接了起来:

          androidx.appcompat.widget.ContentFrameLayout{c38a3bc V.E...... ........ 0,128-1200,1856 #1020002 android:id/content}
            android.widget.RelativeLayout{5692f45 V.E...... ........ 0,0-1200,1728}
              androidx.appcompat.widget.AppCompatButton{e8e569a VFED..C.. ........ 200,200-376,296 #7f070059 app:id/getSize}
              androidx.appcompat.widget.AppCompatButton{619fbcb VFED..C.. ........ 0,296-243,392 #7f07009c app:id/startAnother}
              androidx.appcompat.widget.AppCompatButton{dca3fa8 VFED..C.. ........ 0,392-220,488 #7f070058 app:id/getLocation}

在这里插入图片描述

4)、这里看到AppCompatActivity起到了一个连接的作用,首先它既调用了PhoneWIndow#setContentView,将自己的内部的View层次结构的根View传到PhoneWindow中,在PhoneWindow作为子View加入到了DecorView的层级结构中;另一方面,在自己内部,又负责解析我们的自定义布局,并且将我们的自定义布局的根View作为子View加入到它的View层级结构中。

AppCompatActivity的部分为:

在这里插入图片描述

最终形成了最终的View层级结构:

  DecorView@b8fdc2b[MainActivity]
    android.widget.LinearLayout{f289afc V.E...... ........ 0,0-1080,1344}
      android.view.ViewStub{e6af585 G.E...... ......I. 0,0-0,0 #10201c3 android:id/action_mode_bar_stub}
      android.widget.FrameLayout{4373fda V.E...... ........ 0,74-1080,1344}
        androidx.appcompat.widget.ActionBarOverlayLayout{d96cc0b V.E...... ........ 0,0-1080,1270 #7f07004d app:id/decor_content_parent}
          androidx.appcompat.widget.ContentFrameLayout{6e26ae8 V.E...... ........ 0,112-1080,1270 #1020002 android:id/content}
            android.widget.RelativeLayout{26a6501 V.E...... ........ 0,0-1080,1158}
              androidx.appcompat.widget.AppCompatButton{1d97fa6 VFED..C.. ........ 0,0-176,96 #7f070058 app:id/getSize}
              androidx.appcompat.widget.AppCompatButton{ee41de7 VFED..C.. ........ 0,96-243,192 #7f07009b app:id/startAnother}
          androidx.appcompat.widget.ActionBarContainer{fe74d94 V.ED..... ........ 0,0-1080,112 #7f070029 app:id/action_bar_container}
            androidx.appcompat.widget.Toolbar{397503d V.E...... ........ 0,0-1080,112 #7f070027 app:id/action_bar}
              androidx.appcompat.widget.AppCompatTextView{e0df032 V.ED..... ........ 32,29-300,83}
              androidx.appcompat.widget.ActionMenuView{3cab183 V.E...... ........ 1080,0-1080,112}
            androidx.appcompat.widget.ActionBarContextView{db5af00 G.E...... ......I. 0,0-0,0 #7f07002f app:id/action_context_bar}
    android.view.View{badb339 V.ED..... ........ 0,1344-1080,1440 #1020030 android:id/navigationBarBackground}
    android.view.View{ca6dd7e V.ED..... ........ 0,0-1080,74 #102002f android:id/statusBarBackground}

在这里插入图片描述

3.2.4 总结

DecorView的View层级结构,从上面分析,其实是由三个层级结构组成的:

1)、PhoneWindow根据Activity设置的主题风格,先生成了一个View层级结构,这部分是最顶级的;

2)、我们自己定义的View层级结构,这个是根据我们通过Activity#setContentView中传入的xml类型的layout文件解析出的View层级结构,属于第三级;

3)、AppCompatActivity也有自己的层级结构,这部分属于第二级,在DecorView的View层级结构和我们自定义布局的View层级结构之间起到了连接作用。

另外,我们通过打断点,也可以看到有三次LayoutInflater.inflate调用。

第一次:在PhoneWindow#generateLayout中,这里我们根据Activity的主题风格,选择了一个布局ID,然后调用LayoutInflater.inflate生成了一个View层级结构,根View为:

android.widget.LinearLayout{f289afc V.E...... ........ 0,0-1080,1344}

然后在DecorView#onResourcesLoaded方法中,通过View#addView的方式将该root View作为子View加入到自己的mChildren数组中:

在这里插入图片描述

第二次:由AppCompatActivity调用,生成了AppCompatActivity的View层级结构,根View为:

androidx.appcompat.widget.ActionBarOverlayLayout{d96cc0b V.E...... ........ 0,0-1080,1270 #7f07004d app:id/decor_content_parent} 

然后AppCompatActivity又调用PhoneWindow#setContentVIew将自己的根View作为子View加入到DecorView的View层级结构中去:

在这里插入图片描述

第三次:由AppCompatActivity调用,解析我们在自己定义的Activity中通过Activity#setContentView传入的布局ID,生成了我们定义的View层级结构,根View为:

 android.widget.RelativeLayout{26a6501 V.E...... ........ 0,0-1080,1158}

并且这里在调用LayoutInflater.inflate的时候,指定了parent View为

androidx.appcompat.widget.ContentFrameLayout{6e26ae8 V.E...... ........ 0,112-1080,1270 #1020002 android:id/content}  

从而将我们自定义的View层级结构作为子View加入到了AppCompatActivity的VIew层级结构中:

在这里插入图片描述

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值