美文网首页
聊聊PhoneWindow,getDecorView(),set

聊聊PhoneWindow,getDecorView(),set

作者: sgffsg | 来源:发表于2017-11-03 22:25 被阅读701次

    前言

    最近想实现一下滑动返回,看了一下几个开源的滑动返回的库,或多或少都有点问题,就想自己做一下。看了一下他们的源码,发现直接被activity.getWindow().getDecorView(),activity.getWindow().getDecorView().getChildAt(0),activity.findViewById(Window.ID_ANDROID_CONTENT)整蒙了,它们具体代表一个界面的哪些区域。作为一个三年的Android开发我竟然说不清楚,好惭愧。今天就来总结一下PhoneWindow,activity.getWindow().getDecorView(),statusBar,activity.findViewById(Window.ID_ANDROID_CONTENT)等等它们的边边角角,前生今生。

    分析

    先谈谈window,PhoneWindow,getDecorView()
    这里大部分参考自Android窗口机制(二)Window,PhoneWindow,DecorView,setContentView源码理解

    Window

    它是一个抽象基类,代表顶级窗口的外观及行为策略,这个类的一个实例应该被用作添加到窗口管理器的顶层视图。它提供了标准的UI策略,如背景,标题区域,默认密钥处理等。
    这个抽象类的唯一现有的实现是android.view.PhoneWindow,当你需要一个Window的时候你应该实例化它。

    以上是官方描述,我的理解Window就是一个窗口,最直观的表现就是一个界面的载体。

    延伸一下:
    那么Activity跟Window又是什么关系呢?Activity是我们开发app中打交道最多的一个类,它是一个用户交互界面。那么它怎么是一个用户交互界面呢?你不能说它是它就是,Activity的用户交互体现在setContentView(@LayoutRes int layoutResID)方法,布局xml文件就是界面展现,有布局肯定就是有界面了。使用Activity的setContentView(@LayoutRes int layoutResID)将布局文件与Activity绑定,那么它是怎么绑定的,绑定到哪去了?这里就要用到Window了,Window是一个窗口,它有一个DecorView,而DecorView就是具体承载布局文件的view,后面具体分析

    PhoneWindow

    它是Window的唯一实现类,也就是说Window就是一个抽象,想要具体实现,具体操作还是要靠这个PhoneWindow

    getDecorView()

    这个方法在Window的源码中

    /**
     * Retrieve the top-level window decor view (containing the standard
     * window frame/decorations and the client's content inside of that), which
     * can be added as a window to the window manager.
     *
     * <p><em>Note that calling this function for the first time "locks in"
     * various window characteristics as described in
     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.</em></p>
     *
     * @return Returns the top-level window decor view.
     */
    public abstract View getDecorView();
    

    这个方法是获取到顶层窗口的装饰视图(包含标准窗口框架/装饰以及其内部的客户端内容),可以将其作为窗口添加到窗口管理器。
    那么什么是装饰视图DecorView呢,

    /** @hide */
    public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
          ...
        DecorView(Context context, int featureId, PhoneWindow window,
                WindowManager.LayoutParams params) {
            super(context);
            mFeatureId = featureId;
    
            mShowInterpolator = AnimationUtils.loadInterpolator(context,
                    android.R.interpolator.linear_out_slow_in);
            mHideInterpolator = AnimationUtils.loadInterpolator(context,
                    android.R.interpolator.fast_out_linear_in);
    
            mBarEnterExitDuration = context.getResources().getInteger(
                    R.integer.dock_enter_exit_duration);
            mForceWindowDrawsStatusBarBackground = context.getResources().getBoolean(
                    R.bool.config_forceWindowDrawsStatusBarBackground)
                    && context.getApplicationInfo().targetSdkVersion >= N;
            mSemiTransparentStatusBarColor = context.getResources().getColor(
                    R.color.system_bar_background_semi_transparent, null /* theme */);
    
            updateAvailableWidth();
    
            setWindow(window);
    
            updateLogTag(params);
    
            mResizeShadowSize = context.getResources().getDimensionPixelSize(
                    R.dimen.resize_shadow_size);
            initResizingPaints();
        }
          ...
    }
    

    在PhoneWindow里面,出现了成员变量DecorView,它是一个window的顶层视图,DecorView继承于FrameLayout,我们那些标题栏,内容栏,顶级上看是加载在DecorView上的。而DecorView则是由PhoneWindow负责添加。

    setContentView

    上面我们有个疑问,Activity的setContentView(@LayoutRes int layoutResID)将布局文件与Activity绑定,那么它是怎么绑定的,绑定到哪去了?这里我们就好好分析一下Activity的setContentView,说Activity是一个界面,那只是抽象的描述,具体还是要体现在布局和view上。一个Activity包含一个Window,这个Window的实例化就是PhoneWindow,PhoneWindow中又有一个DecorView,DecorView继承自FrameLayout,好了现在布局有了,往里面添加View那不就是布局和View都有了,那不就是可以组成一个完整的界面了。请看源码娓娓道来
    下面是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();
        }
    

    指向的是Window的setContentView

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

    也就是PhoneWindow的具体实现

    @Override
        public void setContentView(int layoutResID) {
            // 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)) {
                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;
        }
    

    我们看到当mContentParent为null的时候会执行installDecor()第一次进来mContentParent肯定为null

    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 = (TextView) 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);
                        }
                    }
                }
                ...
            }
        }
    

    通过看installDecor的源码我们发现,这个方法中做了两件事:
    生成DecorView
    mDecor = generateDecor(-1);
    和生成布局
    mContentParent = generateLayout(mDecor);

    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().getResources());
                    if (mTheme != -1) {
                        context.setTheme(mTheme);
                    }
                }
            } else {
                context = getContext();
            }
            return new DecorView(context, featureId, this, getAttributes());
        }
    

    generateDecor这个方法我们不用多看,也没什么东西,我们只需要知道生成一个DecorView的实例名字叫mDecor就行了

    protected ViewGroup generateLayout(DecorView decor) {
           。。。
    
            // 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;
            } 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();
            }
    
            // 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;
        }
    

    generateLayout这个方法里面的东西太多,我前面删除一部分
    这里layoutResource,是根据我们设置的状态栏主题,判断DecorView自动为我们加载什么样的布局。当我们将主题设置为NoTitleBar时,generateLayout方法中的layoutResource变量值为R.layout.screen_simple,所以我们看下系统这个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>
    

    当拿到这个layoutResource之后,执行的是mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    //DecorView的onResourcesLoaded方法
    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
            mStackId = getStackId();
    
            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();
        }
    

    这里我们看到执行了一下mDecorCaptionView = createDecorCaptionView(inflater);

    // Free floating overlapping windows require a caption.
        private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
            DecorCaptionView decorCaptionView = null;
            for (int i = getChildCount() - 1; i >= 0 && decorCaptionView == null; i--) {
                View view = getChildAt(i);
                if (view instanceof DecorCaptionView) {
                    // The decor was most likely saved from a relaunch - so reuse it.
                    decorCaptionView = (DecorCaptionView) view;
                    removeViewAt(i);
                }
            }
            final WindowManager.LayoutParams attrs = mWindow.getAttributes();
            final boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
                    attrs.type == TYPE_APPLICATION || attrs.type == TYPE_DRAWN_APPLICATION;
            // Only a non floating application window on one of the allowed workspaces can get a caption
            if (!mWindow.isFloating() && isApplication && StackId.hasWindowDecor(mStackId)) {
                // Dependent on the brightness of the used title we either use the
                // dark or the light button frame.
                if (decorCaptionView == null) {
                    decorCaptionView = inflateDecorCaptionView(inflater);
                }
                decorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);
            } else {
                decorCaptionView = null;
            }
    
            // Tell the decor if it has a visible caption.
            enableCaption(decorCaptionView != null);
            return decorCaptionView;
        }
    

    上面的代码我也没看懂,但是看结果是mDecorCaptionView的创建结果还是空,当mDecorCaptionView == null的时候会把我们加载出来的final View root = inflater.inflate(layoutResource, null);添加到DecorView的第0个位置,也就是一个FrameLayout视图最下面,就是是把刚才那个R.layout.screen_simple文件添加到DecorView最下面,也许看下面的Hierarchy View视图更直观一些,而且我们还看到了DecorView里面有一个LinearLayout并且index为0,细心的会发现DecorView还包含了NavigationBar View 还有StatusBar View,这些东西是什么时候加进去的呢?这个先不管,也可以自己去看DecorView的源码刨根问底。


    image.png

    现在我们在拐回到刚才的generateLayout方法
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    其实就是执行的getDecorView().findViewById,这个东西是谁呢?就是前面加载的R.layout.screen_simple文件里面的FrameLayout android:id="@android:id/content"。最后contentParent作为generateLayout方法的返回赋值给mContentParent = generateLayout(mDecor);

    /**
         * Finds a view that was identified by the id attribute from the XML that
         * was processed in {@link android.app.Activity#onCreate}.  This will
         * implicitly call {@link #getDecorView} for you, with all of the
         * associated side-effects.
         *
         * @return The view if found or null otherwise.
         */
        @Nullable
        public View findViewById(@IdRes int id) {
            return getDecorView().findViewById(id);
        }
    

    写了一大圈我们再回到PhoneWindow的setContentView(int layoutResID)方法,执行mLayoutInflater.inflate(layoutResID, mContentParent);将我们Activity的R.layout.activity_main布局加载到mContentParent里面去,再看下面的图也许会更清晰一些。

    EF0FA9DD-9CAA-4BA2-BD0D-ED9BE3F3405C.png

    回答几个问题

    现在来回答几个疑问
    1,activity.findViewById(Window.ID_ANDROID_CONTENT)拿到的是谁?
    答:本文中拿到的是R.layout.sample布局文件的FrameLayout,其实根据不同的主题拿到不同的布局文件中的FrameLayout,它用来存放我们setContentView(id)时加载的布局
    2,activity.getWindow().getDecorView().getChildAt(0)拿到的是谁?
    答:本文中拿到的是R.layout.sample布局文件,其实根据不同的主题设置拿到不同的布局文件
    3,statusBar在哪里?
    答:在DecorView里面,可以去看上面的Hierarchy View视图,也可以自己使用Hierarchy View看自己布局的视图更直观些。

    参考文章

    http://blog.csdn.net/fuuckwtu/article/details/6519689
    http://blog.csdn.net/yanbober/article/details/45970721
    http://blog.csdn.net/mr_liabill/article/details/49534851
    http://www.jianshu.com/p/983eb8d5bb1a
    http://www.jianshu.com/p/afa921d8ed24
    http://blog.csdn.net/hohohong/article/details/54412464

    相关文章

      网友评论

          本文标题:聊聊PhoneWindow,getDecorView(),set

          本文链接:https://www.haomeiwen.com/subject/usqtmxtx.html