美文网首页Android窗口机制面试大全Android资源收录
Android窗口机制(二)Window,PhoneWindow

Android窗口机制(二)Window,PhoneWindow

作者: Hohohong | 来源:发表于2017-01-13 10:11 被阅读7757次

    Android窗口机制系列

    Android窗口机制(一)初识Android的窗口结构
    Android窗口机制(二)Window,PhoneWindow,DecorView,setContentView源码理解
    Android窗口机制(三)Window和WindowManager的创建与Activity
    Android窗口机制(四)ViewRootImpl与View和WindowManager
    Android窗口机制(五)最终章:WindowManager.LayoutParams和Token以及其他窗口Dialog,Toast

    前篇文章中出现了PhoneWindow,DecorView这些类,如果是第一次见过的话,肯定会觉得陌生。这篇文章主要跟大家讲解Window,PhoneWindow,DecorView他们的理解以及他们之间的联系

    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.
     */
    public abstract class Window {
        ...
        @Nullable
        public View findViewById(@IdRes int id) {
            return getDecorView().findViewById(id);
        }
    
    /**
     * 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);
         ...
    }
    

    一个顶级窗口查看和行为的一个抽象基类。这个类的实例作为一个顶级View添加到Window Manager。它提供了一套标准的UI方法,比如添加背景,标题等等。当你需要用到Window的时候,你应该使用它的唯一实现类PhoneWindow。可以看到,Window是一个抽象基类,它提供了一系列窗口的方法,比如设置背景,标题等等,而它的唯一实现类则是PhoneWindow

    PhoneWindow

    Window的唯一实现类

    public class PhoneWindow extends Window implements MenuBuilder.Callback {
    
        private final static String TAG = "PhoneWindow";
    
        ...
    
        // This is the top-level view of the window, containing the window decor.
        private DecorView mDecor;
    
        // 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.
        private ViewGroup mContentParent;
    
        private ViewGroup mContentRoot;
        ...
    }
    

    可以看到,在PhoneWindow里面,出现了成员变量DecorView的而这里,DecorView则是PhoneWindow里面的一个内部类,它是继承与FrameLayout

     private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    
            /* package */int mDefaultOpacity = PixelFormat.OPAQUE;
    
            /** The feature ID of the panel, or -1 if this is the application's DecorView */
            private final int mFeatureId;
    
            private final Rect mDrawingBounds = new Rect();
    
            private final Rect mBackgroundPadding = new Rect();
    
            private final Rect mFramePadding = new Rect();
    
            private final Rect mFrameOffsets = new Rect();
            ....
     }
    

    既然是FrameLayout,也就可以加载布局文件,也就是说,我们那些标题栏,内容栏,顶级上看是加载在DecorView上的。而DecorView则是由PhoneWindow负责添加

    两者关系以及setContentView源码流程

    接下我们就从一个常见的方法中去认知他们之间的关系,那就是activity里面的setContentView,就是我们平常把布局内容显示到界面上的一个方法。点击activity.setContentView时

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

    里面方法调用了getWindow().setContentView,而这个getWindow方法获取的就是Activity上的Window

    /**
         * Retrieve the current {@link android.view.Window} for the activity.
         * This can be used to directly access parts of the Window API that
         * are not available through Activity/Screen.
         *
         * @return Window The current window, or null if the activity is not
         *         visual.
         */
        public Window getWindow() {
            return mWindow;
        }
    

    可以看到如果当前mWindow为null的话,则表示当前Activity不在窗口上,这里的mWindow.setContentView,实际上调用到的是它的实现类方法phoneWindow.setContentView

     @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) {
                //创建DecorView,并添加到mContentParent上
                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 {
                //将要加载的资源添加到mContentParent上
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                //回调通知表示完成界面加载
                cb.onContentChanged();
            }
        }
    

    如果当前内容还未放置到窗口,此时mContentParent==null,也就是第一次调用的时候,调用那个installDecor方法。FEATURE_CONTENT_TRANSITIONS,则是标记当前内容加载有没有使用过度动画,也就是转场动画。如果内容已经加载过,并且不需要动画,则会调用removeAllViews。添加完Content后如有设置了FEATURE_CONTENT_TRANSITIONS则添加Scene来过度启动。否则mLayoutInflater.inflate(layoutResID, mContentParent);将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中。既然是第一次启动则会调用到installDecor,从字面上看可以知道该方法用来添加DecorView,看下里面说明

    private void installDecor() {
            if (mDecor == null) {
            //调用该方法创建new一个DecorView
                mDecor = generateDecor();
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                mDecor.setIsRootNamespace(true);
                if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                    mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                }
            }
            //一开始DecorView未加载到mContentParent,所以此时mContentParent=null
            if (mContentParent == null) {
            //该方法将mDecorView添加到Window上绑定布局
                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);
                    
                    ...//添加其他资源
                    ...//设置转场动画
            }
        }
    

    可以看到该方法,先通过吊桶generateDecor创建DecorView

    protected DecorView generateDecor() {
            return new DecorView(getContext(), -1);
        }
    

    创建完后再通过调用generateLayout将setContentView的内容赋值到mContentParent,这个方法有点长,我们看下

     protected ViewGroup generateLayout(DecorView decor) {
            // Apply data from current theme.
            //根据当前设置的主题来加载默认布局
            TypedArray a = getWindowStyle();
            //如果你在theme中设置了window_windowNoTitle,则这里会调用到,其他方法同理,
            //这里是根据你在theme中的设置去设置的
            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_windowFullscreen, false)) {
                setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
            }
            
            ...//省略其他加载资源
            
            // 添加布局到DecorView,前面说到,DecorView是继承与FrameLayout,它本身也是一个ViewGroup,而我们前面创建它的时候,只是调用了new DecorView,此时里面并无什么东西。而下面的步奏则是根据用户设置的Feature来创建相应的默认布局主题。举个例子,如果我在setContentView之前调用了requestWindowFeature(Window.FEATURE_NO_TITLE),这里则会通过getLocalFeatures来获取你设置的feature,进而选择加载对应的布局,此时则是加载没有标题栏的主题,对应的就是R.layout.screen_simple
    
            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 {
                // Embedded, so no decoration is needed.
                layoutResource = R.layout.screen_simple;
                // System.out.println("Simple!");
            }
    
            mDecor.startChanging();
            //选择对应布局创建添加到DecorView中
            View in = mLayoutInflater.inflate(layoutResource, null);
            decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            mContentRoot = (ViewGroup) in;
            ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
            ...
            return contentParent;
        }
    

    首先generateLayout会根据当前用户设置的主题去设置对应的Feature,接着,根据对应的Feature来选择加载对应的布局文件,(Window.FEATURE_NO_TITLE)接下来通过getLocalFeatures来获取你设置的feature,进而选择加载对应的布局,这也就是为什么我们要在setContentView之前调用requesetFeature的原因。此时则是加载没有标题栏的主题,对应的就是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里面包含了两个,因为设置可NoTitle,所以上面只有一个ViewStub,否则还有一个FrameLayout。也证明前面第一篇中说的,“DecorView只有一个子元素为LinearLayout。代表整个Window界面,包含通知栏,标题栏,内容显示栏三块区域。”注意FrameLayout里面的id,@android:id/content ,我们setContentView的内容就是添加到这个FrameLayout中。

    generateLayout的返回是contentParent,而它的获取则是ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);`

    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
    

    正是id为content的FrameLayout。之后我们setContentView则是添加在mContentParent上面了。回到前面刚开始的方法

     @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) {
                //创建DecorView,并添加到mContentParent上
                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 {
                //将要加载的资源添加到mContentParent上
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                //回调通知表示完成界面改变
                cb.onContentChanged();
            }
        }
    

    此时已经创建完DecorView并且获取到mContentParent,接着就是将你setContentView的内容添加到mContentParent中,也就是

     mLayoutInflater.inflate(layoutResID, mContentParent);
     或者
     mContentParent.addView(view, params);
    

    最后调用Callback来通知界面发生改变。Callback是Window里面的一个接口,里面声明了当界面更改触摸时调用的各种方法。这里的话,我们看下onContentChanged,在PhoneWindow里面并没有看到onContentChanged的实现类,而我们又知道Activity本身又是加载在Window上的,我们看下Activity

    public class Activity extends ContextThemeWrapper
            implements LayoutInflater.Factory2,
            Window.Callback, KeyEvent.Callback,
            OnCreateContextMenuListener, ComponentCallbacks2,
            Window.OnWindowDismissedCallback { ... }
    

    可以看到Activity里面实现了Window.Callback接口而里面onContentChanged则是空的,也就是我们可以通过重写该方法来监听布局内容的改变了

     public void onContentChanged() {
        }
    

    小结

    • Window是一个抽象类,提供了各种窗口操作的方法,比如设置背景标题ContentView等等
    • PhoneWindow则是Window的唯一实现类,它里面实现了各种添加背景主题ContentView的方法,内部通过DecorView来添加顶级视图
    • 每一个Activity上面都有一个Window,可以通过getWindow获取
    • DecorView,顶级视图,继承与FramentLayout,setContentView则是添加在它里面的@id/content里
    • setContentView里面创建了DecorView,根据Theme,Feature添加了对应的布局文件
    • 当setContentView设置显示后会回调Activity的onContentChanged方法

    回顾

    再看一下前一篇文章的结构图,是不是就更好理解了呢。

    Paste_Image.png

    下一篇文章
    Android窗口机制(三)Window和WindowManager的创建与Activity
    http://www.jianshu.com/p/6afb0c17df43

    相关文章

      网友评论

      • 郭海萍:这个图有问题, DecorView下根据调用者设置的属性,还会addView一层,之后titleView,contentView在其里面
      • 橘子香蕉苹果:不知道可不可以理解成 Activity和phonewindow和decorView ,她们三个的宽和高大小都是一样的?
        开心包:@橘子香蕉苹果 不能这么理解吧,我理解activity是没有宽高这个概念,每个activity有一个phonewindow,phonewindow有contentRect和visibilityRect等矩形区域,decorView宽高应该等同phonewindow中的contentRect
      • 六怀雁:楼主,你上面说
        protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
        }
        DecorView是这么被实例化的,这块不对吧,window没传进DecorView中,这样事件分发需要用到window的callback,DecorView没有持有window,事件如何分发呢?我看到的源码是这样的,
        return new DecorView(context, featureId, this, getAttributes());
        77364e71608e:你的API是多少,我的6.0中,DecorView是PhoneWindow的内部类,自然可以拿到window的callback。
      • 风之翼_f02d:“DecorView只有一个子元素为LinearLayout。代表整个Window界面……”,
        元素是ViewGroup类型,不同布局,对应的类型不一样
      • 红发_SHANKS:可以看到,在PhoneWindow里面,出现了成员变量DecorView的而这里,DecorView则是PhoneWindow里面的一个内部类,它是继承与FrameLayout
        ===============================================================
        现在decorView已经被放出来了,单独成为一个类了
        你需要一台永动机:@阿木123 安卓7之后,decorView直接独立出来了
        0f100e7189b2:每个版本的源码不太一样的,估计LZ 看的是5.0 版本,你是6.0的?
      • 嘿大魔王:"创建完后再通过调用generateLayout将decorView添加到mContentParent,这个方法有点长,我们看下"这一句似乎有问题,在generateLayout中有:
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        inflate布局之后是添加到decorview上的,而mcontentParent是布局中的framelayout。
        所以我觉得是将mcontentParent添加到decor上,而且mcontentParent是一个viewgroup
        Hohohong:没错,已改正。是将setContentView的内容赋值到mContentParent

      本文标题:Android窗口机制(二)Window,PhoneWindow

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