精华内容
下载资源
问答
  • Android DecorView学习
    2021-05-28 03:03:56

    DecorView是activity窗口的根视图,本文介绍DecorView的初始化以及和actionbar,contentview,viewRootImpl的关系

    1.DecorView的视图结构

    6a3bca1b36e8

    图片1.png

    每个activity都对应一个窗口window,这个窗口是PhoneWindow的实例,PhoneWindow对应的布局是DecirView,是一个FrameLayout,DecorView内部又分为两部分,一部分是ActionBar,另一部分是ContentParent,即activity在setContentView对应的布局。

    2.DecorView的初始化

    2.1Activity的setContentView

    从Activity的源码开始

    public void setContentView(@LayoutRes int layoutResID) {

    getWindow().setContentView(layoutResID);

    initWindowDecorActionBar();

    }

    其中,getWindow()拿到的是PhoneWindow对象,所以继续看PhoneWindow中的方法

    public void setContentView(int layoutResID) {

    if (mContentParent == null) {

    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 {

    mLayoutInflater.inflate(layoutResID, mContentParent);

    }

    mContentParent.requestApplyInsets();

    final Callback cb = getCallback();

    if (cb != null && !isDestroyed()) {

    cb.onContentChanged();

    }

    mContentParentExplicitlySet = true;

    }

    2.2installDecor

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

    ...

    }

    }

    mDecor是PhoneWindow对应的DecorView对象,如果mDecor为空,则调用generateDecor方法完成DecorView的初始化

    protected DecorView generateDecor(int featureId) {

    Context context;

    if (mUseDecorContext) {

    Context applicationContext = getContext().getApplicationContext();

    if (applicationContext == null) {

    context = getContext();

    } else {

    context = new DecorContext(applicationContext, getContext().getResources());

    if (mTheme != -1) {

    context.setTheme(mTheme);

    }

    }

    } else {

    context = getContext();

    }

    return new DecorView(context, featureId, this, getAttributes());

    }

    后面具体的就是DecorView与window和Activity的绑定,不再详细介绍

    2.3ContentParent

    继续看setContent的方法,如果没有特别指定过渡动画相关的参数,则调用LayoutInflater的inflate方法,把mContentParent作为参数传进去,mContentParent是一个ViewGroup对象,即xml文件所对应的layout,LayoutInflater最终会把xml文件解析并复制给mContentParent

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {

    final Resources res = getContext().getResources();

    if (DEBUG) {

    Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("

    + Integer.toHexString(resource) + ")");

    }

    final XmlResourceParser parser = res.getLayout(resource);

    try {

    return inflate(parser, root, attachToRoot);

    } finally {

    parser.close();

    }

    }

    2.4Actionbar

    回到setContentView,看第二个执行的方法

    private void initWindowDecorActionBar() {

    Window window = getWindow();

    // Initializing the window decor can change window feature flags.

    // Make sure that we have the correct set before performing the test below.

    window.getDecorView();

    if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {

    return;

    }

    mActionBar = new WindowDecorActionBar(this);

    mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

    mWindow.setDefaultIcon(mActivityInfo.getIconResource());

    mWindow.setDefaultLogo(mActivityInfo.getLogoResource());

    }

    这里的window依然是phonewindow对象,window.getDecorView()的作用是确保decorview不为空,mActionBar代表actionbar,继续看

    public WindowDecorActionBar(View layout) {

    assert layout.isInEditMode();

    init(layout);

    }

    private void init(View decor) {

    mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(

    com.android.internal.R.id.decor_content_parent);

    if (mOverlayLayout != null) {

    mOverlayLayout.setActionBarVisibilityCallback(this);

    }

    mDecorToolbar = getDecorToolbar(decor.findViewById(com.android.internal.R.id.action_bar));

    mContextView = (ActionBarContextView) decor.findViewById(

    com.android.internal.R.id.action_context_bar);

    mContainerView = (ActionBarContainer) decor.findViewById(

    com.android.internal.R.id.action_bar_container);

    mSplitView = (ActionBarContainer) decor.findViewById(

    com.android.internal.R.id.split_action_bar);

    if (mDecorToolbar == null || mContextView == null || mContainerView == null) {

    throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +

    "with a compatible window decor layout");

    }

    mContext = mDecorToolbar.getContext();

    mContextDisplayMode = mDecorToolbar.isSplit() ?

    CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL;

    // This was initially read from the action bar style

    final int current = mDecorToolbar.getDisplayOptions();

    final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0;

    if (homeAsUp) {

    mDisplayHomeAsUpSet = true;

    }

    ActionBarPolicy abp = ActionBarPolicy.get(mContext);

    setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp);

    setHasEmbeddedTabs(abp.hasEmbeddedTabs());

    final TypedArray a = mContext.obtainStyledAttributes(null,

    com.android.internal.R.styleable.ActionBar,

    com.android.internal.R.attr.actionBarStyle, 0);

    if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) {

    setHideOnContentScrollEnabled(true);

    }

    final int elevation = a.getDimensionPixelSize(R.styleable.ActionBar_elevation, 0);

    if (elevation != 0) {

    setElevation(elevation);

    }

    a.recycle();

    }

    actionbar对应的布局文件是screen_action_bar.xml,在init方法里就会找到其中的子控件或者布局完成初始化

    3.actionBar和contentParent如何添加到decorView

    准确来说,actionBar和contentParent并非是添加到decorView上去的,而是本身就存在于decorView,

    对于有actionBar的activity,decorView的默认布局是screen_action_bar.xml,里面就会包含actionBar和contentParent

    对于没有actionBar的activity,会根据activity所带的参数选择decorView的默认布局,例如screen_simple.xml

    选择decorView的默认布局的相关的判断逻辑是installDecor方法中调用generateLayout完成的,以screen_action_bar.xml为例,可以看一下DecorView的默认布局

    xmlns:android="http://schemas.android.com/apk/res/android"

    android:id="@+id/decor_content_parent"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:splitMotionEvents="false"

    android:theme="?attr/actionBarTheme">

    android:layout_width="match_parent"

    android:layout_height="match_parent" />

    android:id="@+id/action_bar_container"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:layout_alignParentTop="true"

    style="?attr/actionBarStyle"

    android:transitionName="android:action_bar"

    android:touchscreenBlocksFocus="true"

    android:keyboardNavigationCluster="true"

    android:gravity="top">

    android:id="@+id/action_bar"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    style="?attr/actionBarStyle" />

    android:id="@+id/action_context_bar"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:visibility="gone"

    style="?attr/actionModeStyle" />

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    style="?attr/actionBarSplitStyle"

    android:visibility="gone"

    android:touchscreenBlocksFocus="true"

    android:keyboardNavigationCluster="true"

    android:gravity="center"/>

    其中,id为action_bar_container和content分别对应actionBar和contentParent,setContentView()和initWindowDecorActionBar()会完成两者的初始化

    4.decorView建立与viewRootImpl的联系

    viewRootImpl是用于管理activity的view,其成员mView对应的就是activity的decorView,viewRootImpl设置decorView的方法是setView

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

    synchronized (this) {

    if (mView == null) {

    mView = view;

    ...

    }

    }

    }

    在源码中搜索setView,可以在WindowManagerGlobal中找到使用的地方

    public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {

    if (view == null) {

    throw new IllegalArgumentException("view must not be null");

    }

    if (display == null) {

    throw new IllegalArgumentException("display must not be null");

    }

    if (!(params instanceof WindowManager.LayoutParams)) {

    throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");

    }

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

    if (parentWindow != null) {

    parentWindow.adjustLayoutParamsForSubWindow(wparams);

    } else {

    // If there's no parent, then hardware acceleration for this view is

    // set from the application's hardware acceleration setting.

    final Context context = view.getContext();

    if (context != null

    && (context.getApplicationInfo().flags

    & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {

    wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;

    }

    }

    ViewRootImpl root;

    View panelParentView = null;

    synchronized (mLock) {

    // Start watching for system property changes.

    if (mSystemPropertyUpdater == null) {

    mSystemPropertyUpdater = new Runnable() {

    @Override public void run() {

    synchronized (mLock) {

    for (int i = mRoots.size() - 1; i >= 0; --i) {

    mRoots.get(i).loadSystemProperties();

    }

    }

    }

    };

    SystemProperties.addChangeCallback(mSystemPropertyUpdater);

    }

    int index = findViewLocked(view, false);

    if (index >= 0) {

    if (mDyingViews.contains(view)) {

    // Don't wait for MSG_DIE to make it's way through root's queue.

    mRoots.get(index).doDie();

    } else {

    throw new IllegalStateException("View " + view + " has already been added to the window manager.");

    }

    // The previous removeView() had not completed executing. Now it has.

    }

    // If this is a panel window, then find the window it is being

    // attached to for future reference.

    if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&

    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {

    final int count = mViews.size();

    for (int i = 0; i < count; i++) {

    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {

    panelParentView = mViews.get(i);

    }

    }

    }

    root = new ViewRootImpl(view.getContext(), display);

    view.setLayoutParams(wparams);

    mViews.add(view);

    mRoots.add(root);

    mParams.add(wparams);

    // do this last because it fires off messages to start doing things

    try {

    root.setView(view, wparams, panelParentView);

    } catch (RuntimeException e) {

    // BadTokenException or InvalidDisplayException, clean up.

    if (index >= 0) {

    removeViewLocked(index, true);

    }

    throw e;

    }

    }

    }

    看addView方法的最后的部分,这里会创建一个viewRootImpl对象root,而view则是decorView,之后就会把decorView设置到viewRootImpl中去,方法addView调用流程有些复杂,与activity与window的创建绑定有关,放到后面再讲。

    更多相关内容
  • 主要解读了Android View源码,为大家详细介绍DecorView与ViewRootImpl,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 如果还不知道DecorView,那也没有什么关系 ^_^ 先来看看实现的效果 实现的大致思路 首先需要明白什么是DecorView,他是android中界面的根布局。其实android的activity界面整个就是一个控件树,DecorView是根节点...
  • 安卓悬浮按钮,依附于decorView不需要各种权限!你懂的!同时支持拖拽,长按事件自动靠边 唯一缺陷在于依附当前activity因此在activity切换的时候会覆盖,但是相比于各种蛋疼的权限 我觉得这点儿是可以接受的 来来来 先...
  • 上篇分析了DecorView创建过程,大致说了其子View构成,还剩下一些疑点,本篇继续分析。 通过本篇文章,你将了解到: 1、DecorView各个子View具体布局内容 2、状态栏(背景)和导航栏(背景)如何添加到DecorView里 3、...

    前言

    上篇分析了DecorView创建过程,大致说了其子View构成,还剩下一些疑点,本篇继续分析。

    通过本篇文章,你将了解到:

    1、DecorView各个子View具体布局内容
    2、状态栏(背景)和导航栏(背景)如何添加到DecorView里
    3、DecorView子View位置与大小的确定
    4、常见的获取DecorView各个区块大小的方法

    DecorView各个子View具体布局内容

    照旧,打开Tools->Layout Inspector
    image.png
    此时,DecorView有三个子View,分别是LinearLayout、navigationBarBackground、statusBarBackground。

    默认DecorView布局

    先来看看LinearLayout,之前分析过加载DecorView时,根据不同的feature确定不同的布局,我们的demo加载的是默认布局:R.layout.screen_simple。
    这是系统自带的布局文件,在哪找呢?

    切换到Project模式——>找到External Libraries——>对应的编译API——>res library root——>layout文件夹下——>寻找对应的布局名

    R.layout.screen_simple布局内容

    <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方向是垂直的,有个属性android:fitsSystemWindows=“true”(后续需要用到)
    • ViewStub是占位用的,默认是Gone,先不管
    • 有个id="content"的FrameLayout,是不是有点熟悉?

    再来看看实际的layout展示:
    image.png

    正好和LinearLayout对应,ViewStub也对得上,但是明明布局文件里的FrameLayout是没有子View的,实际怎么会有呢?当然是中途动态添加进去的。

    SubDecor

    之前分析过,DecorView创建成功后,又继续加载了一个布局:R.layout.abc_screen_toolbar,并赋予subDecor变量,最后将subDecor里的某个子View添加到DecorView里。那么该布局文件在哪找呢?按照上面的方法,你会发现layout里并没有对应的布局文件。

    实际上加载R.layout.abc_screen_toolbar是由AppCompatDelegateImpl.java完成的,而该类属于androidx.appcompat.app包,因此该寻找androidx里资源文件

    image.png
    R.layout.abc_screen_toolbar布局内容:

    <androidx.appcompat.widget.ActionBarOverlayLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/decor_content_parent"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true">
        <include layout="@layout/abc_screen_content_include"/>
        <androidx.appcompat.widget.ActionBarContainer
                android:id="@+id/action_bar_container"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentTop="true"
                style="?attr/actionBarStyle"
                android:touchscreenBlocksFocus="true"
                android:gravity="top">
            <androidx.appcompat.widget.Toolbar
                    android:id="@+id/action_bar"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
             app:navigationContentDescription="@string/abc_action_bar_up_description"
                    style="?attr/toolbarStyle"/>
            <androidx.appcompat.widget.ActionBarContextView
                    android:id="@+id/action_context_bar"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:visibility="gone"
                    android:theme="?attr/actionBarTheme"
                    style="?attr/actionModeStyle"/>
        </androidx.appcompat.widget.ActionBarContainer>
    </androidx.appcompat.widget.ActionBarOverlayLayout>
    

    同样提取几个关键信息:

    • ActionBarOverlayLayout 继承自ViewGroup,id=“decor_content_parent”,同样有个属性:android:fitsSystemWindows=“true”
    • ActionBarContainer顾名思义是容纳ActionBar的,id=“action_bar_container”,android:gravity=“top”。继承自FrameLayout。有两个子View,一个是ToolBar,另一个是ActionBar。现在高版本都使用ToolBar替代ActionBar。

    ActionBarOverlayLayout还有个子View

    <include layout="@layout/abc_screen_content_include"/>
    

    其内容为:

    <merge xmlns:android="http://schemas.android.com/apk/res/android">
        <androidx.appcompat.widget.ContentFrameLayout
                android:id="@id/action_bar_activity_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:foregroundGravity="fill_horizontal|top"
                android:foreground="?android:attr/windowContentOverlay" />
    </merge>
    
    • ContentFrameLayout继承自FrameLayout,id=“action_bar_activity_content”

    以上,DecorView默认布局文件和SubDecor布局文件已经分析完毕,接下来看看SubDecor如何添加到DecorView里。

            //寻找subDecor子布局,命名为contentView
            final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                    R.id.action_bar_activity_content);
            //找到window里content布局,实际上找的是DecorView里名为content的布局
            final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
            if (windowContentView != null) {
                //挨个移除windowContentView的子View,并将之添加到contentView里
                while (windowContentView.getChildCount() > 0) {
                    final View child = windowContentView.getChildAt(0);
                    windowContentView.removeViewAt(0);
                    contentView.addView(child);
                }
                //把windowContentView id去掉,之前名为content
                windowContentView.setId(View.NO_ID);
                //将"content"名赋予contentView
                contentView.setId(android.R.id.content);
            }
            //把subDecor添加为Window的contentView,实际上添加为DecorView的子View。该方法后面再具体分析
            mWindow.setContentView(subDecor);
    

    1、首先从subDecor里寻找R.id.action_bar_activity_content,属于subDecor子View,其继承自FrameLayout。
    2、再从DecorView里寻找android.R.id.content,是FrameLayout
    3、移除android.R.id.content里的子View,并将其添加到R.id.action_bar_activity_content里(当然此时content没有子View)
    4、把"android.R.id.content"这名替换掉R.id.action_bar_activity_content
    5、最后将subDecor添加到FrameLayout里,对就是名字被换掉了的布局。

    此时DecorView和subDecor已经结合了,并且android.R.id.content也存在,我们在setContentView(xx)里设置的layout会被添加到android.R.id.content里。

    状态栏(背景)和导航栏(背景)

    前面只是分析了LinearLayout及其子View的构造,而DecorView还有另外两个子View:状态栏(背景)/导航栏(背景)没有提及,接下来看看它们是如何关联上的。
    既然是DecorView的子View,那么必然有个addView()的过程,搜索后确定如下方法:

    DecorView.java
        private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
                                        int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin,
                                        boolean animate, boolean force) {
            View view = state.view;
            //确定View的宽高
            int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
            int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
            //确定View的Gravity
            int resolvedGravity = verticalBar
                    ? (seascape ? state.attributes.seascapeGravity : state.attributes.horizontalGravity)
                    : state.attributes.verticalGravity;
    
            if (view == null) {
                if (showView) {
                    //构造View
                    state.view = view = new View(mContext);
                    //设置View背景色
                    setColor(view, color, dividerColor, verticalBar, seascape);
                    //设置id
                    view.setId(state.attributes.id);
                    LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
                            resolvedGravity);
                    //添加到DecorView
                    addView(view, lp);
                }
            } else {
                //省略...
            }
            //省略
        }
    

    该方法根据条件添加子View到DecorView,调用该方法的地方有两处:

    DecorView.java
        WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
            WindowManager.LayoutParams attrs = mWindow.getAttributes();
            //控制状态栏、导航栏标记
            int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
    
            if (!mWindow.mIsFloating || isImeWindow) {
                //insets记录着状态栏、导航栏、高度
                if (insets != null) {
                    //四个边界的偏移
                    mLastTopInset = getColorViewTopInset(insets.getStableInsetTop(),
                            insets.getSystemWindowInsetTop());
                    mLastBottomInset = getColorViewBottomInset(insets.getStableInsetBottom(),
                            insets.getSystemWindowInsetBottom());
                    mLastRightInset = getColorViewRightInset(insets.getStableInsetRight(),
                            insets.getSystemWindowInsetRight());
                    mLastLeftInset = getColorViewRightInset(insets.getStableInsetLeft(),
                            insets.getSystemWindowInsetLeft());
                    //省略..
                }
                //省略
                //导航栏高度
                int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset, mLastLeftInset);
                //添加/设置导航栏
                updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
                        calculateNavigationBarColor(), mWindow.mNavigationBarDividerColor, navBarSize,
                        navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge,
                        0 /* sideInset */, animate && !disallowAnimate,
                        mForceWindowDrawsBarBackgrounds);
                //添加设置状态栏
                updateColorViewInt(mStatusColorViewState, sysUiVisibility,
                        calculateStatusBarColor(), 0, mLastTopInset,
                        false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset,
                        animate && !disallowAnimate,
                        mForceWindowDrawsBarBackgrounds);
            }
            //省略 主要和和全屏、隐藏等属性相关的
            
            //mContentRoot是DecorView的第一个子View
            //也即是LinearLayout,根据状态栏、导航栏高度调整LinearLayout高度
            if (mContentRoot != null
                    && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
                MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
                if (lp.topMargin != consumedTop || lp.rightMargin != consumedRight
                        || lp.bottomMargin != consumedBottom || lp.leftMargin != consumedLeft) {
                    lp.topMargin = consumedTop;
                    lp.rightMargin = consumedRight;
                    lp.bottomMargin = consumedBottom;
                    lp.leftMargin = consumedLeft;
                    mContentRoot.setLayoutParams(lp);
                }
            }
            return insets;
        }
    

    提取要点如下:

    • 状态栏、导航栏是属于View,而不是ViewGroup。因此不能再添加任何子View,这也就是为什么称为:状态栏背景,导航栏背景的原因。实际上,DecorView里设置的这两个背景是为了占位使用的。
    • 状态栏、导航栏高度是系统确定的,在ViewRootImpl->setView(xx),获取到其边界属性。
    • DecorView有三个子View,LinearLayout(内容)、状态栏、导航栏。LinearLayout根据后两者状态调整自身的LayoutParms。比如此时LinearLayout bottomMargin=126(导航栏高度)。
    • 重点:DecorView只是给状态栏和导航栏预留位置,俗称背景,我们可以操作背景,但不能操作内容。真正的内容,比如电池图标、运营商图标等是靠系统填充上去的

    再用图表示状态栏、导航栏添加流程:
    image.png
    ViewRootImpl相关请查看:Android Activity创建到View的显示过程

    状态栏/导航栏 如何确定位置呢?

        public static final ColorViewAttributes STATUS_BAR_COLOR_VIEW_ATTRIBUTES =
                new ColorViewAttributes(SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
                        Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
                        Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
                        com.android.internal.R.id.statusBarBackground,
                        FLAG_FULLSCREEN);
    
        public static final ColorViewAttributes NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES =
                new ColorViewAttributes(
                        SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
                        Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT,
                        Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
                        com.android.internal.R.id.navigationBarBackground,
                        0 /* hideWindowFlag */);
    

    预先设置属性,在updateColorViewInt(xx)设置View的Gravity。
    导航栏:Gravity.BOTTOM
    状态栏:Gravity.TOP
    这样,导航栏和状态栏在DecorView里的位置确定了。

    DecorView子View位置与大小的确定

    DecorView三个直接子View添加流程已经确定,通过Layout Inspector看看其大小与位置:
    image.png

    从上图两个标红的矩形框分析:
    LinearLayout 上边界是顶到屏幕,而下边界的与导航栏的顶部平齐,而状态栏是盖在LinearLayout上的,这也就是为什么我们可以设置沉浸式状态栏的原因。
    ContentFrameLayout包含了内容区域,ContentFrameLayout上边界与标题栏底部对齐,下边界充满父控件。
    来看看代码里如何确定LinearLayout和FrameLayout位置:

    #View.java
        public WindowInsets onApplyWindowInsets(WindowInsets insets) {
            if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
                //fitSystemWindows(xx)里面调用fitSystemWindowsInt(xx)
                if (fitSystemWindows(insets.getSystemWindowInsetsAsRect())) {
                    return insets.consumeSystemWindowInsets();
                }
            } else {
                if (fitSystemWindowsInt(insets.getSystemWindowInsetsAsRect())) {
                    return insets.consumeSystemWindowInsets();
                }
            }
            return insets;
        }
    
        private boolean fitSystemWindowsInt(Rect insets) {
            //对应属性android:fitsSystemWindows="true"
            if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
                mUserPaddingStart = UNDEFINED_PADDING;
                mUserPaddingEnd = UNDEFINED_PADDING;
                Rect localInsets = sThreadLocal.get();
                if (localInsets == null) {
                    localInsets = new Rect();
                    sThreadLocal.set(localInsets);
                }
                boolean res = computeFitSystemWindows(insets, localInsets);
                mUserPaddingLeftInitial = localInsets.left;
                mUserPaddingRightInitial = localInsets.right;
                //最终根据insets来设定该View的padding
                //设置padding,这里是设置paddingTop
                internalSetPadding(localInsets.left, localInsets.top,
                        localInsets.right, localInsets.bottom);
                return res;
            }
            return false;
        }
    

    LinearLayout设置了android:fitsSystemWindows=“true”,当状态栏展示的时候,需要将LinearLayout设置为适配状态栏,此处设置paddingTop=“状态栏高度”
    加上之前设置的marginBottom="导航栏高度”,这就确定了LinearLayout位置。

    ContentFrameLayout父控件是ActionBarOverlayLayout,因此它的位置受父控件控制,ActionBarOverlayLayout计算标题栏占的位置,而后设置ContentFrameLayout marginTop属性。

    针对上面的布局,对应的用图说话:
    image.png

    常见的获取DecorView各个区块大小的方法

    既然知道了DecorView各个子View的布局,当然就有相应的方法获取其大小。

    DecorView的尺寸

    只要能获取到DecorView对象,一切都不在话下。
    常见的通过Activity或者View获取:
    Activity:

    getWindow().getDecorView()
    

    View:

    getRootView()
    

    导航栏/状态栏尺寸:

    导航栏/状态栏高度是由系统确定的,固化在资源字段里:

        public static int getStatusBarHeight(Context context) {
            int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
            return context.getResources().getDimensionPixelSize(resourceId);
        }
    
        public static int getNavigationBarHeight(Context context) {
            Resources resources = context.getResources();
            int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
            int height = resources.getDimensionPixelSize(resourceId);
            return height;
        }
    

    总结

    两篇文章分析了DecorView创建到展示一些布局细节。了解了DecorView的构成,我们做出一些效果更得心应手,如:状态栏沉浸/隐藏、Activity侧滑关闭、自定义通用标题栏等。
    注:以上关于DecorView、subDecor、标题栏、布局文件和区块尺寸的选择是基于当前的demo的。可能你所使用的主题、设置的属性和本文不同导致布局效果差异,请注意甄别
    源码基于:Android 10.0

    展开全文
  • window与decorview window与decorview的逻辑关系如下: 一个Activity对应一个PhoneWindow,PhoneWidnow会处理这个activity中的ui展示和 用户的行为(如触摸,点击等)。 PhoneWidnow不是一个View对象,通过将...

    概述

    window是android中非常常见的一个概念。Activity、Dialog、Toast这些常用的知识点都是和window密不可分的。
    因此,笔者整理了下window相关的知识,期望能对需要的读者有所帮助。

    window官方描述

    Window源码中对window的描述如下:

    /**
     * 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.
     */
    

    笔者翻译后的大致意思如下:

    • window是一个抽象类,主要用来处理窗口的展示与行为策略(比如触摸,点击等)。
    • window类的实例应该是一个被添加到windowManager的顶级视图。
    • window提供了标准的UI策略,如背景,标题区域,默认密钥处理等。
    • widnow唯一的实现类是android.view.PhoneWindow,如果要使用window就必须通过android.view.PhoneWindow。

    window与decorview

    window与decorview的逻辑关系如下:

    • 一个Activity对应一个PhoneWindow,PhoneWidnow会处理这个activity中的ui展示和 用户的行为(如触摸,点击等)。
    • PhoneWidnow不是一个View对象,通过将PhoneWindow添加到windowManager中,PhoneWindow能够将要处理的行为事件传递给DecorView。
    • DecorView继承自FrameLayout,是除了Window之外最顶级的视图。
    • ContentView就是我们通常使用activity.setContentView()中设置的View。它所对应的id是R.id.content。
      在这里插入图片描述

    源码解析

    接下来就通过源码解析看下具体的window相关的层级之间的关系。

    Activity.setContentView()

    逻辑如下:

    1. 调用window.setContentView() 来初始化Layout
    2. 调用initWindowDecorActionBar来初始化 actionBar

    由于是关注window相关层级之间的关系,因此接下来直接看window.setContentView的源码。

        public void setContentView(@LayoutRes int layoutResID) {
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
        }
    

    PhoneWindow.setContentView()

    根据上文解析的Window的注释,已知window的实例是PhoneWindow,因此直接跳转到PhoneWindow的源码。

    主要逻辑如下:

    1. 首先判断contentView是否已经创建,如果没有创建就需要调用installDecor()来初始化DecorView。
    2. 然后会调用inflate()方法,将layout添加到contentView中。(FEATURE_CONTENT_TRANSITIONS是转场动画相关逻辑,暂且不看。)
    3. 最后调用window的callBack的onContentChanged()方法,通知window内容的更改。
        // This is the view in which the window contents are placed. It is either
        // mDecor itself, or a child of mDecor where the contents go.
        ViewGroup mContentParent;
    
        public void setContentView(int layoutResID) {
            if (mContentParent == null) {
                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 {
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    PhoneWindow.installDecor()

    installDecor()是初始化DecorView的相关逻辑,主要逻辑如下:

    1. 判断DecorView是否为空,如果为空就调用generateDecor()来创建。如果已经初始化过了,那么就再调用setWindow绑定一下此时的window。
    2. 判断contentView是否为空,如果为空,就调用generateLayout()来创建。
    3. decorContentParent就是toolbar的View,也是通过判断是否为空来执行初始化逻辑。
    4. 而后的FEATURE_ACTIVITY_TRANSITIONS就是转场动画相关的逻辑,暂且不看。
        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.makeOptionalFitsSystemWindows();
    
                final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                        R.id.decor_content_parent);
    
                if (decorContentParent != null) {
                    mDecorContentParent = decorContentParent;
                    mDecorContentParent.setWindowCallback(getCallback());
                    if (mDecorContentParent.getTitle() == null) {
                        mDecorContentParent.setWindowTitle(mTitle);
                    }
    
                    final int localFeatures = getLocalFeatures();
                    for (int i = 0; i < FEATURE_MAX; i++) {
                        if ((localFeatures & (1 << i)) != 0) {
                            mDecorContentParent.initFeature(i);
                        }
                    }
    
                    mDecorContentParent.setUiOptions(mUiOptions);
    
                    if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
                            (mIconRes != 0 && !mDecorContentParent.hasIcon())) {
                        mDecorContentParent.setIcon(mIconRes);
                    } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
                            mIconRes == 0 && !mDecorContentParent.hasIcon()) {
                        mDecorContentParent.setIcon(
                                getContext().getPackageManager().getDefaultActivityIcon());
                        mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
                    }
                    if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
                            (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
                        mDecorContentParent.setLogo(mLogoRes);
                    }
    
                    // Invalidate if the panel menu hasn't been created before this.
                    // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
                    // being called in the middle of onCreate or similar.
                    // A pending invalidation will typically be resolved before the posted message
                    // would run normally in order to satisfy instance state restoration.
                    PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
                    if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) {
                        invalidatePanelMenu(FEATURE_ACTION_BAR);
                    }
                } else {
                    mTitleView = findViewById(R.id.title);
                    if (mTitleView != null) {
                        if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                            final View titleContainer = findViewById(R.id.title_container);
                            if (titleContainer != null) {
                                titleContainer.setVisibility(View.GONE);
                            } else {
                                mTitleView.setVisibility(View.GONE);
                            }
                            mContentParent.setForeground(null);
                        } else {
                            mTitleView.setText(mTitle);
                        }
                    }
                }
    
                if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
                    mDecor.setBackgroundFallback(mBackgroundFallbackResource);
                }
    
                // Only inflate or create a new TransitionManager if the caller hasn't
                // already set a custom one.
                if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
                    if (mTransitionManager == null) {
                        final int transitionRes = getWindowStyle().getResourceId(
                                R.styleable.Window_windowContentTransitionManager,
                                0);
                        if (transitionRes != 0) {
                            final TransitionInflater inflater = TransitionInflater.from(getContext());
                            mTransitionManager = inflater.inflateTransitionManager(transitionRes,
                                    mContentParent);
                        } else {
                            mTransitionManager = new TransitionManager();
                        }
                    }
    
                    mEnterTransition = getTransition(mEnterTransition, null,
                            R.styleable.Window_windowEnterTransition);
                    mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
                            R.styleable.Window_windowReturnTransition);
                    mExitTransition = getTransition(mExitTransition, null,
                            R.styleable.Window_windowExitTransition);
                    mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
                            R.styleable.Window_windowReenterTransition);
                    mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
                            R.styleable.Window_windowSharedElementEnterTransition);
                    mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
                            USE_DEFAULT_TRANSITION,
                            R.styleable.Window_windowSharedElementReturnTransition);
                    mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
                            R.styleable.Window_windowSharedElementExitTransition);
                    mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
                            USE_DEFAULT_TRANSITION,
                            R.styleable.Window_windowSharedElementReenterTransition);
                    if (mAllowEnterTransitionOverlap == null) {
                        mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
                                R.styleable.Window_windowAllowEnterTransitionOverlap, true);
                    }
                    if (mAllowReturnTransitionOverlap == null) {
                        mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
                                R.styleable.Window_windowAllowReturnTransitionOverlap, true);
                    }
                    if (mBackgroundFadeDurationMillis < 0) {
                        mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
                                R.styleable.Window_windowTransitionBackgroundFadeDuration,
                                DEFAULT_BACKGROUND_FADE_DURATION_MS);
                    }
                    if (mSharedElementsUseOverlay == null) {
                        mSharedElementsUseOverlay = getWindowStyle().getBoolean(
                                R.styleable.Window_windowSharedElementsUseOverlay, true);
                    }
                }
            }
        }
    

    PhoneWindow.generateDecor()

    generateDecor()是创建DecorView的相关逻辑,主要逻辑如下:

    1. 先判断此时的context是否是DecorContext,如果不是,就通过application和此时的content来new 一个DecorContext。
    2. 直接new一个DecorView返回,需要传入参数DecorContext 和此时的window。
        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, getContext());
                    if (mTheme != -1) {
                        context.setTheme(mTheme);
                    }
                }
            } else {
                context = getContext();
            }
            return new DecorView(context, featureId, this, getAttributes());
        }
    

    PhoneWindow.generateLayout()

    generateLayout()是创建contentView的方法,主要逻辑如下:

    1. 通过getWindowStyle()获取到window的TypeArray,然后通过这个TypeArray来获取window相关的状态,从而来设置window的属性。(常见的状态如:是否有title,是否有actionbar等)
    2. 此处还有window.getContainer()的逻辑。举个例子,一个dialog会有自己的Window,Dialog的Window的Container就是 它的Activity的Window。
    3. 最终会通过id=ID_ANDROID_CONTENT获取到contentParent返回。ID_ANDROID_CONTENT = com.android.internal.R.id.content。(使用Fragement的时候经常会使用R.id.content,其实就是对应contentView)
        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);
            }
    
            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);
            }
    
            if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
                requestFeature(FEATURE_ACTION_BAR_OVERLAY);
            }
    
            if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
                requestFeature(FEATURE_ACTION_MODE_OVERLAY);
            }
    
            if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
                requestFeature(FEATURE_SWIPE_TO_DISMISS);
            }
    
            if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
                setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
            }
    
            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_windowOverscan, false)) {
                setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags()));
            }
    
            if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
                setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
            }
    
            if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch,
                    getContext().getApplicationInfo().targetSdkVersion
                            >= android.os.Build.VERSION_CODES.HONEYCOMB)) {
                setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
            }
    
            a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
            a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
            if (DEBUG) Log.d(TAG, "Min width minor: " + mMinWidthMinor.coerceToString()
                    + ", major: " + mMinWidthMajor.coerceToString());
            if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
                if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
                a.getValue(R.styleable.Window_windowFixedWidthMajor,
                        mFixedWidthMajor);
            }
            if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {
                if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
                a.getValue(R.styleable.Window_windowFixedWidthMinor,
                        mFixedWidthMinor);
            }
            if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {
                if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
                a.getValue(R.styleable.Window_windowFixedHeightMajor,
                        mFixedHeightMajor);
            }
            if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {
                if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
                a.getValue(R.styleable.Window_windowFixedHeightMinor,
                        mFixedHeightMinor);
            }
            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 targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;
            final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
            final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
            final boolean targetHcNeedsOptions = context.getResources().getBoolean(
                    R.bool.target_honeycomb_needs_options_menu);
            final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);
    
            if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {
                setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE);
            } else {
                setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE);
            }
    
            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);
            }
    
            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.mForceWindowDrawsStatusBarBackground) {
                    params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
                }
            }
            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);
            }
            if (a.hasValue(R.styleable.Window_windowLayoutInDisplayCutoutMode)) {
                int mode = a.getInt(R.styleable.Window_windowLayoutInDisplayCutoutMode, -1);
                if (mode < LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
                        || mode > LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER) {
                    throw new UnsupportedOperationException("Unknown windowLayoutInDisplayCutoutMode: "
                            + a.getString(R.styleable.Window_windowLayoutInDisplayCutoutMode));
                }
                params.layoutInDisplayCutoutMode = mode;
            }
    
            if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
                    >= android.os.Build.VERSION_CODES.HONEYCOMB) {
                if (a.getBoolean(
                        R.styleable.Window_windowCloseOnTouchOutside,
                        false)) {
                    setCloseOnTouchOutsideIfNotSet(true);
                }
            }
    
            if (!hasSoftInputMode()) {
                params.softInputMode = a.getInt(
                        R.styleable.Window_windowSoftInputMode,
                        params.softInputMode);
            }
    
            if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
                    mIsFloating)) {
                /* All dialogs should have the window dimmed */
                if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
                    params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
                }
                if (!haveDimAmount()) {
                    params.dimAmount = a.getFloat(
                            android.R.styleable.Window_backgroundDimAmount, 0.5f);
                }
            }
    
            if (params.windowAnimations == 0) {
                params.windowAnimations = a.getResourceId(
                        R.styleable.Window_windowAnimationStyle, 0);
            }
    
            // The rest are only done if this window is not embedded; otherwise,
            // the values are inherited from our container.
            if (getContainer() == null) {
                if (mBackgroundDrawable == null) {
                    if (mBackgroundResource == 0) {
                        mBackgroundResource = a.getResourceId(
                                R.styleable.Window_windowBackground, 0);
                    }
                    if (mFrameResource == 0) {
                        mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
                    }
                    mBackgroundFallbackResource = a.getResourceId(
                            R.styleable.Window_windowBackgroundFallback, 0);
                    if (false) {
                        System.out.println("Background: "
                                + Integer.toHexString(mBackgroundResource) + " Frame: "
                                + Integer.toHexString(mFrameResource));
                    }
                }
                if (mLoadElevation) {
                    mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
                }
                mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
                mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
            }
    
            // Inflate the window decor.
    
            int layoutResource;
            int features = getLocalFeatures();
            // System.out.println("Features: 0x" + Integer.toHexString(features));
            if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
                layoutResource = R.layout.screen_swipe_dismiss;
                setCloseOnSwipeEnabled(true);
            } else 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");
            }
    
            if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
                ProgressBar progress = getCircularProgressBar(false);
                if (progress != null) {
                    progress.setIndeterminate(true);
                }
            }
    
            if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
                registerSwipeCallbacks(contentParent);
            }
    
            // Remaining setup -- of background and title -- that only applies
            // to top-level windows.
            if (getContainer() == null) {
                final Drawable background;
                if (mBackgroundResource != 0) {
                    background = getContext().getDrawable(mBackgroundResource);
                } else {
                    background = mBackgroundDrawable;
                }
                mDecor.setWindowBackground(background);
    
                final Drawable frame;
                if (mFrameResource != 0) {
                    frame = getContext().getDrawable(mFrameResource);
                } else {
                    frame = null;
                }
                mDecor.setWindowFrame(frame);
    
                mDecor.setElevation(mElevation);
                mDecor.setClipToOutline(mClipToOutline);
    
                if (mTitle != null) {
                    setTitle(mTitle);
                }
    
                if (mTitleColor == 0) {
                    mTitleColor = mTextColor;
                }
                setTitleColor(mTitleColor);
            }
    
            mDecor.finishChanging();
    
            return contentParent;
        }
    

    activity中window的初始化

    window是在activity启动的时候被初始化的,widnow初始化的任务栈调用是:

    • ActivityThread.performLaunchActivity()
    • Activity.attach()

    Activity.attach()主要逻辑如下:

    1. 创建PhoneWindow
    2. 为PhoneWindow设置windowManager,通过context.getSystemService()获取到windowManager。
    3. 当activity的mParent存在时,为window的container设置成mParent.getWindow()。(mParent和TabActivity有关,有兴趣的可以自行看下TabActivity源码)
        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) {
            attachBaseContext(context);
    
            mFragments.attachHost(null /*parent*/);
    
            mWindow = new PhoneWindow(this, window, activityConfigCallback);
            mWindow.setWindowControllerCallback(this);
            mWindow.setCallback(this);
            mWindow.setOnWindowDismissedCallback(this);
            mWindow.getLayoutInflater().setPrivateFactory(this);
            if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
                mWindow.setSoftInputMode(info.softInputMode);
            }
            if (info.uiOptions != 0) {
                mWindow.setUiOptions(info.uiOptions);
            }
            mUiThread = Thread.currentThread();
    
            mMainThread = aThread;
            mInstrumentation = instr;
            mToken = token;
            mAssistToken = assistToken;
            mIdent = ident;
            mApplication = application;
            mIntent = intent;
            mReferrer = referrer;
            mComponent = intent.getComponent();
            mActivityInfo = info;
            mTitle = title;
            mParent = parent;
            mEmbeddedID = id;
            mLastNonConfigurationInstances = lastNonConfigurationInstances;
            if (voiceInteractor != null) {
                if (lastNonConfigurationInstances != null) {
                    mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
                } else {
                    mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                            Looper.myLooper());
                }
            }
    
            mWindow.setWindowManager(
                    (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                    mToken, mComponent.flattenToString(),
                    (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
            if (mParent != null) {
                mWindow.setContainer(mParent.getWindow());
            }
            mWindowManager = mWindow.getWindowManager();
            mCurrentConfig = config;
    
            mWindow.setColorMode(info.colorMode);
    
            setAutofillOptions(application.getAutofillOptions());
            setContentCaptureOptions(application.getContentCaptureOptions());
        }
    
    展开全文
  • 我们都知道DecorView是最顶层View(根View),它是怎么创建和使用的呢? 通过本篇文章,你将了解到: 1、DecorView创建过程。 2、DecorView与Window/Activity关系 3、DecorView各个子View创建 DecorView创建过程 ...

    前言

    我们都知道DecorView是最顶层View(根View),它是怎么创建和使用的呢?
    通过本篇文章,你将了解到:

    1、DecorView创建过程。
    2、DecorView与Window/Activity关系
    3、DecorView各个子View创建

    DecorView创建过程

    来回顾一下Activity创建过程:
    image.png

    AMS管理着Activity生命周期,每当切换Activity状态时通过Binder告诉ActivityThread,ActivityThread通过Handler切换到主线程(UI线程)执行,最终分别调用到我们熟知的onCreate(xx)/onResume()方法。
    更多细节请移步:Android Activity创建到View的显示过程
    本次关注的重点是setContentView(xx)方法。

    简单布局分析

    相信大家都知道,该方法是将我们布局(layout)文件添加到一个id为**“android.R.id.content”**的ViewGroup里,我们只需要关心layout的内容。那么R.id.content是整个View树的根布局吗?来看看一个最简单的Activity的布局:

    my_layout.xml
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:background="@color/colorGreen"
        android:id="@+id/my_root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <Button
            android:id="@+id/btn"
            android:text="hello"
            android:onClick="onClick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
        </Button>
    </FrameLayout>
    

    效果图:
    image.png
    把布局观察器打开:
    image.png
    可以看出,"content"布局对应的是ContentFrameLayout,它并不是View树的根布局,根布局是DecorView,DecorView和ContentFrameLayout之间还有几个View,接下来我们就来了解上图的这些View是怎么确定的。

    setContentView(xx)源码解析

    从Activity onCreate(xx)看起

    AppCompatDelegateImpl

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            //先调用父类构造函数
            //初始化一些必要变量
            super.onCreate(savedInstanceState);
            setContentView(R.layout.layout_path);
        }
    

    创建及初始化AppCompatDelegateImpl

    AppCompatActivity.java
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            final AppCompatDelegate delegate = getDelegate();
            delegate.installViewFactory();
            //交给AppCompatDelegate代理
            delegate.onCreate(savedInstanceState);
            super.onCreate(savedInstanceState);
        }
    

    AppCompatActivity将工作交给了AppCompatDelegate处理,而AppCompatDelegate是抽象类,真正工作的是其子类:AppCompatDelegateImpl。

        @Override
        public void onCreate(Bundle savedInstanceState) {
            ensureWindow();
        }
        private void ensureWindow() {
            //省略
            //mHost为创建此代理的Activity
            if (mWindow == null && mHost instanceof Activity) {
                attachToWindow(((Activity) mHost).getWindow());
            }
        }
        private void attachToWindow(@NonNull Window window) {
            if (mWindow != null) {
                throw new IllegalStateException(
                        "AppCompat has already installed itself into the Window");
            }
            //接收各种事件的window callback,实际就是将callback再包了一层
            mAppCompatWindowCallback = new AppCompatWindowCallback(callback);
            window.setCallback(mAppCompatWindowCallback);
            //省略
            //使用activity的window
            mWindow = window;
        }
    

    上面代码的主要工作是关联AppCompatDelegateImpl和Activity的window变量。

    接着来看看setContentView(R.layout.layout_path)本尊
    Activity的setContentView(xx)最终调用了AppCompatDelegateImpl里的setContentView(xx):

    AppCompatDelegateImpl.java
        @Override
        public void setContentView(int resId) {
            //创建SubDecor,从名字可以猜测一下是DecorView的子View
            ensureSubDecor();
            //找到R.id.content布局
            ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
            //清空content里的子View
            contentParent.removeAllViews();
            //加载在activity里设置的layout,并添加到content里
            LayoutInflater.from(mContext).inflate(resId, contentParent);
        }
    

    可以看出,我们自定义的layout最后被添加到contentParent里,而contentParent是从mSubDecor里找寻的,因此我们重点来看看ensureSubDecor()方法。
    ensureSubDecor里调用的是createSubDecor()方法来创建subDecor。

    subDecor创建

    AppCompatDelegateImpl.java
        private ViewGroup createSubDecor() {
            //根据Activity的主题设置不同设置window的feature
            TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
            //确保window已经关联
            ensureWindow();
            //获取DecorView,没有则创建
            mWindow.getDecorView();
            ViewGroup subDecor = null;
            //有标题
            if (!mWindowNoTitle) {
                //根据条件给subDecor加载不同的布局文件
                if (mIsFloating) {
                    subDecor = (ViewGroup) inflater.inflate(
                            R.layout.abc_dialog_title_material, null);
                } else if (mHasActionBar) {
                    //加载subDecor布局
                    subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                            .inflate(R.layout.abc_screen_toolbar, null);
    
                    //寻找subDecor子布局
                    mDecorContentParent = (DecorContentParent) subDecor
                            .findViewById(R.id.decor_content_parent);
                    mDecorContentParent.setWindowCallback(getWindowCallback());
                }
            } else {
                //省略
            }
            //标题栏标题
            if (mDecorContentParent == null) {
                mTitleView = (TextView) subDecor.findViewById(R.id.title);
            }
            //寻找subDecor子布局,命名为contentView
            final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                    R.id.action_bar_activity_content);
            //找到window里content布局,实际上找的是DecorView里名为content的布局
            final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
            if (windowContentView != null) {
                //挨个移除windowContentView的子View,并将之添加到contentView里
                while (windowContentView.getChildCount() > 0) {
                    final View child = windowContentView.getChildAt(0);
                    windowContentView.removeViewAt(0);
                    contentView.addView(child);
                }
                //把windowContentView id去掉,之前名为content
                windowContentView.setId(View.NO_ID);
                //将"content"名赋予contentView
                contentView.setId(android.R.id.content);
            }
            //把subDecor添加为Window的contentView,实际上添加为DecorView的子View。该方法后面再具体分析
            mWindow.setContentView(subDecor);
            return subDecor;
        }
    

    该方法较长,省略了一些细枝末节,主体功能是:

    • 根据不同的前置条件,加载不同的布局文件作为subDecor
    • 将subDecor加入到DecorView里,subDecor本身是ViewGroup

    subDecor创建了,那么DecorView啥时候创建呢?createSubDecor()有段代码:

    mWindow.getDecorView()
    

    DecorView创建

    mWindow之前分析过是Activity的window变量,它的实现类是PhoneWindow。

    PhoneWindow.java
        View getDecorView() {
            if (mDecor == null || mForceDecorInstall) {
                installDecor();
            }
            return mDecor;
        }
    
        private void installDecor() {
            //mDecor作为PhoneWindow变量
            if (mDecor == null) {
                //新建DecorView,并关联window
                mDecor = generateDecor(-1);
            }
            if (mContentParent == null) {
                //创建DecorView子布局
                mContentParent = generateLayout(mDecor);
            }
        }
    
        protected DecorView generateDecor(int featureId) {
            Context context;
            if (mUseDecorContext) {
                Context applicationContext = getContext().getApplicationContext();
                if (applicationContext == null) {
                    context = getContext();
                } else {
                    //applicationContext是Application
                    //getContext 是Activity
                    //DecorContext 继承自ContextThemeWrapper
                    context = new DecorContext(applicationContext, getContext());
                    if (mTheme != -1) {
                        context.setTheme(mTheme);
                    }
                }
            } else {
                context = getContext();
            }
            //this 指的是phoneWindow,赋值给mWindow变量
            return new DecorView(context, featureId, this, getAttributes());
        }
    

    从上可知:

    DecorView继承自FrameLayout

    注:DecorContext 有关请移步:Android各种Context的前世今生

    重点来看看

    mContentParent = generateLayout(mDecor);

    PhoneWindow.java
        protected ViewGroup generateLayout(DecorView decor) {
            //获取WindowStyle
            TypedArray a = getWindowStyle();
            //根据style设置各种flags和feature
            //省略...
    
            WindowManager.LayoutParams params = getAttributes();
            //待加载的布局资源id
            int layoutResource;
            int features = getLocalFeatures();
            //根据不同的feature确定不同的布局
            if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
                layoutResource = R.layout.screen_swipe_dismiss;
                setCloseOnSwipeEnabled(true);
            } else if (features) {
                //省略
            } else {
                //默认加载该布局
                layoutResource = R.layout.screen_simple; 
            }
            //实例化上面确定的布局,并将该布局添加到DecorView里
            mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
            //获取名为ID_ANDROID_CONTENT的子View
            //public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
            //实际上就是content
            ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
            return contentParent;
        }
    

    将确定的布局加载,并添加到DecorView里。

        void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
            //实例化布局
            final View root = inflater.inflate(layoutResource, null);
            if (mDecorCaptionView != null) {
                //省略
            } else {
                //添加到DecorView里
                addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            //记录root
            mContentRoot = (ViewGroup) root;
        }
    

    总结来说,mWindow.getDecorView()做了哪些事呢?

    • 创建DecorView,并关联PhoneWindow与DecorView(互相持有)
    • 根据feature确定DecorView子布局,并添加填充满DecorView

    可能看到上面的分析还是一头雾水,没关系先来理一下DecorView和SubDecor关系。

    1、首先创建DecorView,并添加其子View,该子View在DecorView里称为:mContentRoot。当前场景下使用的子View资源id:R.layout.screen_simple;
    2、mContentRoot里有名为"R.id.content"的子View,其在PhoneWindow里称作为:mContentParent
    3、当前场景下使用的subDecor资源id:R.layout.abc_screen_toolbar,实例化该资源文件获得subDecor实例
    4、该subDecor里有子View资源id:R.id.action_bar_activity_content,在AppCompatDelegateImpl里命名为:contentView
    5、DecorView添加subDecor过程:取出DecorView里的mContentParent,并移除里面的子View,并将移除的子View添加到subDecor里的contentView里。
    6、将contentView更名为“R.id.content”
    7、最后通过mWindow.setContentView(subDecor),将subDecor添加到DecorView里

    继续分析mWindow.setContentView(subDecor)

    PhoneWindow.java
        public void setContentView(View view, ViewGroup.LayoutParams params) {
            if (mContentParent == null) {
                //为空,说明DecorView没构造
                installDecor();
            } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                //清空子View
                mContentParent.removeAllViews();
            }
            if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                //省略
            } else {
                //View添加到mContentParent
                mContentParent.addView(view, params);
            }
            //省略
        }
    

    以上就是DecorView的创建过程。
    DecorView创建完成后,再将自定义布局添加到DecorView里名为R.id.content布局里。至此setContentView分析完毕,老规矩用图表示整个流程。

    setContentView图示

    image.png

    DecorView与Window/Activity关系

    Activity:
    我们平时把Activity当做一个页面,直观上看这个页面承载着我们的布局显示以及相应的逻辑处理。Activity有生命周期,在不同的状态下做不同的处理。
    Window
    顾名思义,就是我们所见的窗口,Activity显示的内容实际上是展示在Window上的,对应的是PhoneWindow。
    DecorView
    Window本身是比较抽象的,可以想象成为一个管理的东西,它管理的就是ViewTree,而DecorView是ViewTree的根。我们具体的界面展示是通过View完成的,把设计好的View添加到DecorView里,整个ViewTree就构建起来了,最终添加到Window里。
    因此:

    • Activity管理着Window,Window管理着DecorView,Activity间接管理着DecorView
    • Activity处在“create"状态的时候创建对应的Window,并在setContentView里创建DecorView,最终添加自定义布局到DecorView里。此时,Activity创建完毕,Window创建完毕,DecorView创建完毕,ViewTree构建成功,三者关系已建立。
    • Acitivity处在“resume"状态的时候,通过Window的管理类将DecorView添加到Window里,并提交更新DecorView的请求。当下次屏幕刷新信号到来之时,执行ViewTree三大流程(测量,布局,绘制),最终的效果就是我们的自定义布局显示在屏幕上。
      “注”:指的状态执行时机是处在ActivityThread里的。

    这么看起来还是不太顺,我们所说的三者的联系实际上在代码里来看就是:“不是你持有我就是我引用你”,通过类图来看看三者之间的引用情况:
    image.png

    可以看出,Window作为Activity和DecorView的联结者。
    注:Activity也持有DecorView的引用,只是不对外提供获取的接口。因此一般通过Activity获取Window,再获取DecorView。
    在UI上看来,可以这么理解:
    image.png

    注:这图只是便于理解抽象关系,实际上对于Activity来说并没有尺寸的说法。

    注:以上关于DecorView、subDecor、标题栏、布局文件和区块尺寸的选择是基于当前的demo的。可能你所使用的主题、设置的属性和本文不同导致布局效果差异,请注意甄别。
    源码基于:Android 10.0

    展开全文
  • 一、职能简介 1.Activity Activity并不负责视图控制,它只是控制生命周期和处理...Window是视图的承载器,内部持有一个 DecorView,而这个DecorView才是 view 的根布局。Window是一个抽象类,实际在Activity中持有的
  • DecorView介绍

    千次阅读 2019-05-16 17:34:04
    01.什么是DecorView DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。 DecorView作为顶级View,一般情况下它内部包含一个竖直方向的LinearLayout,在这个LinearLayout里面有上下三个部分...
  • } 用一幅图来表示DecorView的结构如下: 在视图层次中,Activity在WIndow之上,如下图: 小结:DecorView是顶级View,内部有titlebar和contentParent两个子元素,contentParent的id是content,而我们设置的main.xml...
  • #class in PhoneWindow protected ViewGroup generateLayout(DecorView decor) { // 老样子我们又省略了一大堆乱七八糟的代码,此处省略的代码是解析一系列的系统布局属性 ... int layoutResource; ... // 省略的...
  • DecorView与SubDecor的创建加载 Window与Window Manager的创建加载 ViewRootImpl的创建以及视图真正加载 ViewRootImpl的事件分发 一定要在主线程才可以更新UI吗?为什么? Activity的Token和Dialog的关系 Toast机制 ...
  • 即使Activity的布局已经成功添加到DecorView中,DecorView此时还没有添加到Window中 ActivityThread的handleResumeActivity方法中,首先会调用Activity的onResume方法,接着调用Activity的makeVisible()方法 ...
  • Android如何使用DecorView实现对话框功能发布时间:2020-07-20 09:57:50来源:亿速云阅读:102作者:小猪这篇文章主要讲解了Android如何使用DecorView实现对话框功能,内容清晰明了,对此有兴趣的小伙伴可以学习一下...
  • 前言示意图但在绘制前,系统会有一些绘制准备,即前面几个步骤:创建PhoneWindow类、DecorView类、ViewRootmpl类等今天,我将主要讲解View绘制前的准备,主要包括:DecorView创建 & 显示,希望你们会喜欢。1. ...
  • DecorView有很多种预设的布局xml文件,会根据上层类对Window界面样式的设置,会分情况加载不同的布局文件,例如: 1) 对于有actionBar的Activity,decorView的默认布局是screen_action_bar.xml,里面就会包含...
  • 学习于《Android开发艺术探索》,本文讲解了对ViewRoot、DecorView、MeasureSpec的解析
  • DecorView

    2015-03-13 14:22:50
    一、DecorView为整个Window界面的最顶层View。 二、DecorView只有一个子元素为LinearLayout。代表整个Window界面,包含通知栏,标题栏,内容显示栏三块区域。 三、LinearLayout里有两个FrameLayout子元素。  (20...
  • Activity、Window、DecorView 的关系一、概述 一、概述
  • Activity,Window,DecorView简介 关系图 Activity Window DecorView ViewRoot 关联过程简析 Activity->setContentView 简单分析Activity获取window的流程 前提摘要:仅个人笔记整理,参考文章...
  • 相关类: frameworks\base\core\java\android\view\View.java frameworks\base\core\java\android\app\...frameworks\base\core\java\com\android\internal\policy\DecorView.java frameworks\base\core\java...
  • 下面给出源代码: 在installDecor方法中标注1处,如果mDecor为空(mDecor是Window持有的一个成员变量,指的就是DecorView),那么在 generateDecor()中去实例化新建DecorView,我们继续跟进generateDecor()查看源代码...
  • Android窗口机制系列前篇文章中出现了PhoneWindow,DecorView这些类,如果是第一次见过的话,肯定会觉得陌生。这篇文章主要跟大家讲解Window,PhoneWindow,DecorView他们的理解以及他们之间的联系Window我们来看下...
  • 对于 activity 与 DecorView 之间的关系,大家可以看这篇文章: DecorView 是一个应用窗口的根容器,它本质上是一个 FrameLayout。DecorView 有唯一一个子 View,它是一个垂直 LinearLayout,包含两个子元素,一个...
  • 用一幅图来表示DecorView的结构如下: DecorView结构 小结:DecorView是顶级View,内部有titlebar和contentParent两个子元素,contentParent的id是content,而我们设置的main.xml布局则是contentParent里面的一个子...
  • 浅谈DecorView

    2017-11-02 10:55:10
    由以上代码可以看出,该方法还是做了相当多的工作的,首先根据设置的主题样式来设置DecorView的风格,比如说有没有titlebar之类的,接着为DecorView添加子View,而这里的子View则是上面提到的mContentParent,如果...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 21,156
精华内容 8,462
关键字:

DecorView