解析WindowManager

解析WindowManager

       WindowManager是于WindowMangerService(以后简称WMS)关联最紧密的类,因为,为了更好的理解WMS,需要先了解WindowManager的相关知识,本文将基于Android8.1.0系统来介绍WindowManger的体系、Window的属性和Window的操作。

    1. Window、WindowManager 和 WMS

       Window 它是一个抽象类,具体的实现类为 PhoneWindow ,它对 View 进行管理。 WindowManager 是一个接口类,继承自接口 ViewManager ,它是用来管理 Window 的,它的实现类为 WindowManagerImpl 。如果我们想要对 Window (View) 进行添加、更新和删除操作就可以使用 WindowManager, WindowManager 会将具体的工作交由 WMS 来处理, WindowManager 和 WMS 通过 Binder 来进行跨进程通信, WMS 作为系统服务有很多 API 是不会暴露给 WindowManager 的,这一点与 ActivityManager 和 AMS 的关系有些类似。 Window、WindowManager 和 WMS 的关系可以简略地用图1 来表示。

图1 Window、WindowManager 和 WMS 的关系​​​​

       Window 包含了 View 并View 进行管理, Window 用虚线来表示是因为 Window 是一个抽象概念,用来描述一个窗口,并不是真实存在的, Window 的实体其实也是 View 。WindowManager 用来管理 Window ,而 WindowManager 所提供的功能最终会由 WMS 进行处理。

    2. WindowManager 的关联类

       WindowManager 是一个接口类,继承自接口 ViewManager, ViewManager 中定义了 3个方法,分别用来添加、更新和删除 View ,代码如下所示:

frameworks/base/core/java/android/view/ViewManager.java

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
    
       Window Manager 也继承了这些方法,而这些方法传入的参数都是 View 类型,说明 Window 是以 View 的形式存在的。 Window Manager 在继承 View Manager 的同时,又加入很 多功能 ,包括 Window 的 类型和层级相关的常量、内部类以及一些方法,其中有两个方法 是根据 Window 的特性加入的,代码如下所示:
 

frameworks/base/core/java/android/view/WindowManager.java

    public Display getDefaultDisplay();

    public void removeViewImmediate(View view);
       getDefaultDisplay 方法 能够得知这个 Window Manager 实 例将 Window 添加到哪个屏幕上了,换句话说,就是得到 Window Manager 所管理的 屏幕 (Display)。   remove View Immediate 方法则 规定在这个方法返回前要立即执行 View.onDetachedFrom Window() ,来完成传入的 View 相关的销毁工作。
 
       Window 是一 个抽 象类,它的具体实现类为 P hone Window, Phone Window 是何时创建 的呢?在Activity 启动过程中会调 用 ActivityThread 的   performLaunchActivity 方法, performLaunchActivity 方法中 又会调用 A ctivity 的  attach 方 法,P hone Window 就是在 Activity 的 attach 方法中创建的,代码如下所示:
 
frameworks/base/core/java/android/app/Activity.java
    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) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback); // ... 1
        
        ...

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); // ... 2
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);
    }

       在注释1处创建了PhoneWindow,在注释2处调用了PhoneWindow的setWindowManager方法,这个方法在PhoneWindow的父类Window中实现。代码如下所示:

frameworks/base/core/java/android/view/Window.java
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); // ... 1
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); // ... 2
    }
       如果传人的 Window Manager 为  null ,就 会在注释1 处调用 Context 的 getSystemService 方法,并传入服务的名称 Context.WINDOW _SERVICE (值为 window ),具体在 Contextlmpl 中实现,代码如下所示:
 

frameworks/base/core/java/android/app/Contextlmpl.java

    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
       在getSys temService 方法 中会调用 SystemServiceRegistry  getSystemService 方法,代码如下所示:
 
frameworks/base/core/java/android/app/SystemServiceRegistry.java
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); // ... 1
        return fetcher != null ? fetcher.getService(ctx) : null; // ... 2
    }
       SYSTEM_SERVICE_FETCHERS 是一个HashMap 类型的数据,它用来存储服务的ServiceFetcher, 那么传入的 Context.WINDOW_SERVICE 底对应着 什么?我们接 着往 下看,代码如下所示:
 
frameworks/base/core/java/android/app/SystemServiceRegistry.java
final class SystemServiceRegistry {
    ...
    private SystemServiceRegistry() { }
    static {
        ...
        registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx); // ... 1
            }});
        ...
    }
    ...
}
      Sys temService Registry 静态代码块中会调 用多个 reg isterService 方法 ,这里 只列举了和本节有关的一个, 从注释1 处可以 看出,传入的 Context. WINDOW_ SERVICE 对应的就是 Window Manager Imp 实例, registerService方法的代码如下所示:
 
  frameworks/base/core/java/android/app/SystemServiceRegistry.java
    private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher); // ... 1
    }

       registerService方法内部会将传入的服务的名称存入到 SYSTEM_SERVICE_NAMES 中。在注释1处以服务的名称为key,new CachedServiceFetcher<WindowManager>() 匿名对象为value存储在SYSTEM_SERVICE_FETCHERS中,我们回到SystemServiceRegistry getSystemService方法的注释1处得到的fetcher对象实质上指向的就是new CachedServiceFetcher<WindowManager>() 这个匿名对象,通过注释2处的fetcher.getService方法来获取服务,我们接下来查看CachedServiceFetcher类的定义,代码如下所示:

 frameworks/base/core/java/android/app/SystemServiceRegistry.java

    static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
        private final int mCacheIndex;

        public CachedServiceFetcher() {
            mCacheIndex = sServiceCacheSize++;
        }

        @Override
        @SuppressWarnings("unchecked")
        public final T getService(ContextImpl ctx) {
            final Object[] cache = ctx.mServiceCache;
            synchronized (cache) {
                // Fetch or create the service.
                Object service = cache[mCacheIndex];
                if (service == null) {
                    try {
                        service = createService(ctx); // ... 1
                        cache[mCacheIndex] = service;
                    } catch (ServiceNotFoundException e) {
                        onServiceNotFound(e);
                    }
                }
                return (T)service;
            }
        }

        public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
    }

       在注释1处调用了createService方法,而createService方法是一个抽象方法,它在刚才分析的registerService方法中创建的匿名对象new CachedServiceFetcher<WindowManager>() 中实现的,因此我们可以得出结论,在SystemServiceRegistry getSystemService方法的注释2处得到的就是WindowManagerImpl实例。

        再回到  Window 的  set Window Manager 方法 ,在注释1处得到 Window Manager Imp 实 例后 转为 W indow Manager 类型,在注释2处调 用了 WindowManager Imp 的 createLocalWindowManager 方法,代码如下所示:
 
frameworks/base/core/java/android/view/WindowManagerlmpl.java
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }
       createLocalWindow Manager 方法 同样也是创建 Window Manager Impl  ,不同的是这次创建 WindowManagerlmpl 时将创建它的 Window 作为参数传了进来,这样 WindowManagerlmpl 就持有了 Window 的引用,可以对 Window 进行操作,比如在 Window 中添加 View ,会调 WindowManagerlmpl 的 add View 方法 ,如下所示:
 
frameworks/base/core/java/android/view/WindowManagerlmpl.java
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); // ... 1
    }
       在注释1 处调用 WindowManagerGlo bal 的 a dd View 方法,其中最后一个参数 mParentWindow 是上面提到 Window ,可以看处  WindowManagerlmpl 虽然是 WindowManager 的实现类,但是 没有实 现什么功能,而 是将功能委 托给 WindowManagerGlobal ,这 里用 到的 是桥接模式。关于 W indo wMa nagerGlobal 的  add View 方法后面会 进行介绍 。先 来查看在 W indow Manager Impl 中是如何定义 WindowManagerGlobal 的,如下所示:
 

frameworks/base/core/java/android/view/WindowManagerlmpl.java

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); // ... 1
    private final Context mContext;
    private final Window mParentWindow; // ... 2

    private IBinder mDefaultToken;

    public WindowManagerImpl(Context context) {
        this(context, null);
    }

    private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow; // ... 3
    }
}
       在注释1 处通过 WindowManagerGlobal 的 getInstance方法获取mGlobal对象 ,WindowManagerGlobal采用了单例模式,说明在一个进程中只有一个 WindowManagerGlobal 实例。注释2 处的代码结合注释3处的代码说明这个 Window Manager Impl 实例会作为哪个 Window 的子 Window ,这也就说明在一 个进程中 WindowManager Impl 可能会有 多个实例。
       通过如上的惊码分析, Window Manager 的关联类如图2 所示。
图2  WindowManager的关联类
       从图 2 可以看出, Phone Window 继承自 Window, Window 通过 set Window Manager 方法与 Window Manager 发生关联。 Window Manager 继承自接口 ViewManager, WindowManager Imp 是 WindowManag er 接口的 实现类,但是具体 的功能都会委托给 WindowManagerGlobal 来实现。

    3. Window的属性

       Window的属性被定义在WindowManager的内部类LayoutParams 中,了解 Window 的属性能够更好地理解 WMS 的内部原理。 Window 的属性有很多种,与应用开发最密切的有3种,它们分别是 Type (Window 的类型)、 Flag (Window 的标志)和SoftlnputMode (软键盘相关模式),下面分别介绍这3种 Window 的属性。

     3.1 Window 的类型和显示次序

       Window 的类型有很多种,比如应用程序窗口、系统错误窗口、输入法窗口、 PopupWindow 、Toast 、Dialog 等。总的来说 Window 分为三大类型,分别是 application Window (应用程序窗口 )、 Sub Window (子窗口)、 System Window (系统窗口),每个大类型中又包含了很多种类型,它们都定义在 WindowManager 的静态内部类 LayoutParams 中,接下 来分别对这大类型进行讲解。

      1. 应用程序窗口

       Activity 就是一个典型的应用程序窗口,应用程序窗口包含的类型如下所示:

frameworks/base/core/java/android/view/WindowManager.java

      /**
       * Start of window types that represent normal application windows.
       */
      public static final int FIRST_APPLICATION_WINDOW = 1; // ... 1

      /**
       * Window type: an application window that serves as the "base" window
       * of the overall application; all other application windows will
       * appear on top of it.
       * In multiuser systems shows only on the owning user's window.
       *
       * 窗口的基础值,其他的窗口值都要大于这个值
       */
       public static final int TYPE_BASE_APPLICATION   = 1;

       /**
        * Window type: a normal application window.  The {@link #token} must be
        * an Activity token identifying who the window belongs to.
        * In multiuser systems shows only on the owning user's window.
        *
        * 普通的应用程序窗口类型
        */
        public static final int TYPE_APPLICATION        = 2;

        /**
         * Window type: special application window that is displayed while the
         * application is starting.  Not for use by applications themselves;
         * this is used by the system to display something until the
         * application can show its own windows.
         * In multiuser systems shows on all users' windows.
         *
         *  应用程序启动的窗口类型,用于系统在应用程序窗口启动前显示的窗口
         */
        public static final int TYPE_APPLICATION_STARTING = 3;

        /**
         * Window type: a variation on TYPE_APPLICATION that ensures the window
         * manager will wait for this window to be drawn before the app is shown.
         * In multiuser systems shows only on the owning user's window.
         */
        public static final int TYPE_DRAWN_APPLICATION = 4;

        /**
         * End of types of application windows.
         */
        public static final int LAST_APPLICATION_WINDOW = 99; // ... 2
       应用程序窗口共包含了以上几种 Type 值,其中注释1 处的 Type 表 示应用程序窗口类型初始值,注释2 处的 Ty pe 表示应用程序窗口类型结束值,也就是说应用程序窗口 Type 值范围为 1 ~ 99,  这个数值的大小涉及窗口的层级 , 后面会讲到。

       2. 子窗口

       子窗口,顾名思义,它不能独立存在,需要附着 在其他窗 口才可以, Pop up Window 就 属于子窗口。子窗口的类型定义 如下所:
 
frameworks/base/core/java/android/view/WindowManager.java
 

        /**
         * Start of types of sub-windows.  The {@link #token} of these windows
         * must be set to the window they are attached to.  These types of
         * windows are kept next to their attached window in Z-order, and their
         * coordinate space is relative to their attached window.
         *
         * 子窗口类型的初始值
         */
        public static final int FIRST_SUB_WINDOW = 1000;

        /**
         * Window type: a panel on top of an application window.  These windows
         * appear on top of their attached window.
         */
        public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;

        /**
         * Window type: window for showing media (such as video).  These windows
         * are displayed behind their attached window.
         */
        public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;

        /**
         * Window type: a sub-panel on top of an application window.  These
         * windows are displayed on top their attached window and any
         * {@link #TYPE_APPLICATION_PANEL} panels.
         */
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;

        /** Window type: like {@link #TYPE_APPLICATION_PANEL}, but layout
         * of the window happens as that of a top-level window, <em>not</em>
         * as a child of its container.
         */
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;

        /**
         * Window type: window for showing overlays on top of media windows.
         * These windows are displayed between TYPE_APPLICATION_MEDIA and the
         * application window.  They should be translucent to be useful.  This
         * is a big ugly hack so:
         * @hide
         */
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;

        /**
         * Window type: a above sub-panel on top of an application window and it's
         * sub-panel windows. These windows are displayed on top of their attached window
         * and any {@link #TYPE_APPLICATION_SUB_PANEL} panels.
         * @hide
         */
        public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;

        /**
         * End of types of sub-windows.
         *
         * 子窗口类型的结束值
         */
        public static final int LAST_SUB_WINDOW = 1999;

       可以看出子窗口的 Type 值范围为 1000 ~1999。

       3. 系统窗口

       Toast、输入法窗口、系统音量调窗口、系统错误窗口都属于系统窗口。系统窗口的类型定义如下所示:

frameworks/base/core/java/android/view/WindowManager.java

        /**
         * Start of system-specific window types.  These are not normally
         * created by applications.
         *
         * 系统窗口类型的初始值
         */
        public static final int FIRST_SYSTEM_WINDOW     = 2000;

        /**
         * Window type: the status bar.  There can be only one status bar
         * window; it is placed at the top of the screen, and all other
         * windows are shifted down so they are below it.
         * In multiuser systems shows on all users' windows.
         *
         *  系统状态栏窗口
         */
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;

        /**
         * Window type: the search bar.  There can be only one search bar
         * window; it is placed at the top of the screen.
         * In multiuser systems shows on all users' windows.
         * 
         * 系统搜索条窗口
         */
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;

        /**
         * Window type: phone.  These are non-application windows providing
         * user interaction with the phone (in particular incoming calls).
         * These windows are normally placed above all applications, but behind
         * the status bar.
         * In multiuser systems shows on all users' windows.
         * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
         *
         * 系统通话窗口
         */
        @Deprecated
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;

        /**
         * Window type: system window, such as low power alert. These windows
         * are always on top of application windows.
         * In multiuser systems shows only on the owning user's window.
         * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
         *
         * 系统Alert窗口
         */
        @Deprecated
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;

        /**
         * Window type: keyguard window.
         * In multiuser systems shows on all users' windows.
         * @removed
         *
         * 系统锁屏窗口
         */
        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;

        /**
         * Window type: transient notifications.
         * In multiuser systems shows only on the owning user's window.
         * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
         *
         * Toast 窗口
         */
        @Deprecated
        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;


        ...

        /**
         * End of types of system windows.
         */
        public static final int LAST_SYSTEM_WINDOW      = 2999;
       系统窗口的类型值有很多 ,这里只列出了一小部分,系统窗口的 Type 值范围为 2000 ~ 2999。

       4. 窗口的显示次序

       当一个进程向 WMS 申请一个窗口时, WMS 会为窗口确定显示次序。为了方便窗口显示次序的管理,手机屏幕可以虚拟地用 X、Y、Z 轴来表示,其中 Z 轴垂直于屏幕,从屏幕内指向屏幕外,这样确定窗口显示次序也就是确定窗口在 Z 轴上的次序,这个次序称为 Z-Oder。Type 值是 Z-Oder 排序的依据,我们知道应用程序窗口的 Type 值范围为1 ~ 99, 子窗口 1000 ~1999 ,系统窗口 2000 ~ 2999 ,在一般情况下, Type 值越大则 Z-Oder 排序越靠前,就越靠近用户。当然窗口显示次序的逻辑不会这么简单,情况会比较多,这个逻辑不在本文的讨论范围 ,这里我们只需要知道窗口显示次序的基本规则就可以了。

    3.2 Window 的标志

       Window的标志也就是Flag,用于控制Window的显示,同样被定义在WindowManager的内部类LayoutParams中,这里给出几个比较常用的标志,如表1所示:

表1 常用的Window的标志
Flag 描述
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON 只要窗口可见,就允许在开启状态的屏幕上锁屏
FLAG_NOT_FOCUSABLE 窗口不能获取输入焦点,设置该标志的同时,FLAG_NOT_TOUCH_MODAL也会被设置
FLAG_NOT_TOUCHABLE 窗口不接收任何触摸事件
FLAG_NOT_TOUCH_MODAL 将该窗口区域外的触摸事件传递给其他的Window,而自己只会处理窗口区域内的触摸事件
FLAG_KEEP_SCREEN_ON 只要窗口可见,屏幕就会一直亮着
FLAG_LAYOUT_IN_SCREEN 允许窗口超过屏幕之外
FLAG_FULLSCREEN 隐藏所有的屏幕装饰窗口,比如在游戏、播放器中的全屏显示
FLAG_SHOW_WHEN_LOCKED 窗口可以在锁屏的窗口之上显示
FLAG_IGNORE_CHEEK_PRESSES 当用户的脸贴近屏幕时(比如打电话时),不会去响应此事件
FLAG_TURN_SCREEN_ON 窗口显示时将屏幕点亮

       设置 Window 的 Flag 有3 种方法,第一种是通过 Window 的 addFlags 方法:

Window mWindow =getWindow() ; 
mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

       第二种是通过Window的setFlags方法:

Window mWindow = getWindow() ; 
mWindow.setFlags(WindowManager.LayoutParams.FLAG FULLSCREEN, WindowManager.LayoutParams.FLAG FULLSCREEN) ;
       其实 Win dow 的 ad dFlags 方法 内部会调用 setFl ags 方法 ,因此这两种方法区别不大。 第三种则是给 Layou tParams 设置Fl ag ,并通过 Window Manager 的  addView 方法 进行添加, 如下所示:
WindowManager.LayoutParams mWindowLayoutParams = new WindowManager.LayoutParams(); 
mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN ; 
WindowManager mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); 
TextView mTextView = new TextView(this);
mWindowManager.addView(mTextView, mWindowLayoutParams);

     3.3 软键盘相关模式

       窗口和窗口的叠加是十分常见的场景,但如果其中的窗口是软键盘窗口,可能就会出现一些问题,比如典型的用户登录界面,默认的情况是弹出的软键盘窗口可能会盖住输入框下方的按钮,这样用户体验就非常糟糕。为了使得软键盘窗口能够按照期望的方式来显示,WindowManager的静态内部类LayoutParams中定义了软键盘相关模式,这里给出常用的几种模式,如表2所示:

表2 软键盘相关模式
SoftInputMode 描述
SOFT_INPUT_STATE_UNSPECIFIED 没有指定状态,系统会选择一个合适的状态或者依赖于主题的设置
SOFT_INPUT_STATE_UNCHANGED 不会改变软键盘的状态
SOFT_INPUT_STATE_HIDDEN 当用户进入该窗口时,软键盘默认隐藏
SOFT_INPUT_STATE_ALWAYS_HIDDEN 当窗口获取焦点时,软键盘总是被隐藏
SOFT_INPUT_ADJUST_RESIZE 当软键盘弹出时,窗口会调整大小
SOFT_INPUT_ADJUST_PAN 当软键盘弹出时,窗口不需要调整大小,要确保输入焦点时可见的
       从上面给出的 Soft InputMode ,可以发现,  它们与 AndroidManifest 中  Activity 的属性 android:windowSoftInputMode 是对应的。因此,除了在 AndroidMainfest 中为 Activity 设置 android:windowSoftInputMode 以外还可以在 Java 代码中为 Window Soft InputMode ,如下所示:
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

4. Window 的操作

       Window Manager 对  Window 进行管理,说到管理那就离不开对 Window 的添加 、 更新和删除的操作,在这里我们把它们统称为 Window 的操作。对于 Window 的操作,最终都 是交由  WMS 来进行处理的 。窗 口的操作分为两大部分, 一 部分是 Window Manager 处理部 分,另一部分是 WMS 处理部分。我们知道 Window 分为三 大类,分别是 Application Window (应用程序窗口)、 Sub Window (子窗口)和 System Window (系统窗口),对于不同类型的窗口添加过程会有所不同,但是对于 WMS 处理 部分,添 加的过程 基本 上是一 样的, WMS 对于这三大类的窗口基本是“一视同仁”的,如图 3 所示。
图3  Window的操作​​​​

       本篇文章主要讲解Window操作的WIndowManager处理部分。

     

     4.1 系统窗口的添加过程

       系统窗口的添加过程也会根据不同的系统窗口有所区别, 这里以系统窗口 StatusBar 为例, StatusBar 是 SystemUI 的重要组成部分,具体就是指系统状态栏 ,用于显示时间、电量和信号等信息。首先来查看 StatusBar 的 addStatusBarWindow 方法,这个方法负责为 StatusBar 添加 Window ,如下所示:

frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/StatusBar.java

    private void addStatusBarWindow() {
        makeStatusBarView(); // ... 1
        mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
        mRemoteInputController = new RemoteInputController(mHeadsUpManager);
        mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight()); // ... 2
    }
       在注释1 处用 于构建 Stat usBar 的视图 。在注释2处调 用了 Sta tusBarWindowManager 的  add 方法,并将 StatusBar 的视图( PhoneStatusBarView的对象)和 StatusBar 的高度传进去, StatusBarWindowManager  的  add 方法如下所示:
 
frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
    public void add(View statusBarView, int barHeight) {

        // Now that the status bar window encompasses the sliding panel and its
        // translucent backdrop, the entire thing is made TRANSLUCENT and is
        // hardware-accelerated.
        mLp = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                barHeight,
                WindowManager.LayoutParams.TYPE_STATUS_BAR, // ... 1
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                        | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                PixelFormat.TRANSLUCENT);
        mLp.token = new Binder();
        mLp.gravity = Gravity.TOP;
        mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
        mLp.setTitle("StatusBar");
        mLp.packageName = mContext.getPackageName();
        mStatusBarView = statusBarView;
        mBarHeight = barHeight;
        mWindowManager.addView(mStatusBarView, mLp); // ... 2
        mLpChanged = new WindowManager.LayoutParams();
        mLpChanged.copyFrom(mLp);
    }
       首先通过创建 LayoutParams 来配置  StatusBar 视图的属性,包括 Width、 Height、 Type、Flag、Gravity、 Soft InputMode 等。 关键 在注释1 处,设置了  TYPE_ STATUS_ BAR 表示 StatusBar 视图的窗 口类型是状态栏 。在注释2调 用了 Window Manager 的  add View 方法 , addView 方法 定义 Window Manager 的父类接口 View Manager 中,而 addView 方法 则是在 WindowManager Impl 实 现的,代码如下所示:
 

frameworks/base/core/java/android/WindowManagerlmpl.java

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

       addView方法的第一个参数的类型为 View ,说明窗口也是以 View 形式 存在的,addView 方法中会调用 WindowManagerGlobal 的 addView方法 ,代码如下所示:

frameworks/base/core/java/android/view/WindowManagerGlobal.java


    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams); // ... 1
        } 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) {
            ...

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

            view.setLayoutParams(wparams);

            mViews.add(view); // ... 3
            mRoots.add(root); // ... 4
            mParams.add(wparams); // ... 5

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView); // ... 6
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
       在介绍 addView 方法前首先要了解 WindowManagerGlobal 中维护的和 Window 操作相关的3个列表,在窗口的添加、更新和删除过程中都会 涉及这3 个列表,它们分别是 View 列表( ArrayList<View > mViews )、布局参数列表 Array List < Window Manager.LayoutParams > rnParams )和 ViewRootlmpl 列表( ArrayList < ViewRootlmpl > mRoots )。了解 了这3个列表 后,我们接 着分析  add View 方法 ,首 先会对参数 view para ms 和 display进行检查,此处代码省略了。在注释1处,如果 当前 窗口要作为 子窗口,  就会根 据父窗口对子窗 口的 WindowManager.LayoutParams 类型的  wparam 对象进行相应的调 整。在 注释3 处将添加的 View 保存到 View 列表中,在 注释5 将窗 口的 参数保存 布局参数 列表中 。在注释2处 创建了 ViewRootlmp 并赋值给 root ,在 注释4 处将 root  存 入到  ViewRootlmpl 列表中,在 注释6 处将 窗口和窗 口的参数 通过 set View 方法 设置到  View Rootlmpl 中,可见我们添加窗 口这一 操作是通过 ViewRootlmpl 来 进行的。 ViewRootlmpl 有很多功能,主要有一下几点
       1.  View 树的根并管理  View树。
       2. 触发 View 的测 量、 布局和绘制。
       3. 输入事 件的中转站。
       4. 管理 Surface。
       5. 负责与  WMS 进行进程间通信。
 
       了解了 ViewRootimpl 的主要功能之后, 我们接着 查看 ViewRootimpl 的  set View 方法,代码如下所示
 
frameworks/base/core/java/android/view/ViewRootlmpl.java
 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                ...
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                  }
                ...
             }
            ...
        }
    }
       在 set View 方法中有很多 逻辑 ,这里 只截取了一 小部分,主 要就是调用了 mWindowSession 的  addToDisplay 方法,m WindowSession 是  IWindowSession 类 型的,它是一个Binder 对象,  用于进行进程间通信, IWindowSession 是  Client 端的代理,它的 Server 端的实现为 Session ,此前的代码逻辑都 运行 在本 地进程的,而 Session 的  addToDisplay 方法则运行在 WMS 所在的进程( SystemServer进程  )中,如图4所 示。
图4  ViewRootImpl 与 WMS通信
       本地进程的 ViewRootlmpl 要想和 WMS 进行通信需要经过 Session, 那么 Session 为何包含在 WMS 中呢?我们接 着往 下看 Session 的  addToDisplay 方法 ,代码如下所示:
 

 frameworks/base/services/core/java/com/android/server/wm/Session.java

    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
       在 addToDisplay 方法 中调用了 WMS 的  add Window 方法 ,并将自身也就是 Session 作 为参数传了进去,每个应用程序进程都会对应一个 Session, WMS 会用 Array List 来保存这些 Session ,这就是为什么图 4 中的 WMS 包含 Session 的原因 。这样剩 下的工作就交给 WMS 来处理,在 WMS 会为这个添 加的窗口分配 Surface ,并确定窗 口显示次序,可见负责显示界面的是画布 Surface ,而不是窗 口本 身。 WMS 会将 它所 管理的  Surface 交由 SurfaceFlinger 处理, Surface Flinger 会将这些 Surface 混 合并绘制到 屏幕上。

    4.2 Activity的添加过程

       无论是哪种窗口,它的添加过程在 WMS 处理部分中基本是类似的,只不过会在权限和窗口显示次序等方面会有些不同。但是在 Window Manager 处理部分会有所不同,这里以最典型的应用程序窗口 Activity 为例, Activity 在启动过程中,如果 Activity 所在的进程不存在则会创建新的进程,创建新的进程之后就会运行代表主线程的实例 ActivityThread, ActivityThread 管理着 当前应用程序进程的线程,这在 Activity 的启动过程中运用得很明显, 当界面要与用户进行交互时,会调用 ActivityThread的    handleResumeActivity 方法, 如下所示:
 
frameworks/base/core/java/android/app/ActivityThread.java

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ...
        r = performResumeActivity(token, clearHide, reason); // ... 1

        if (r != null) {
            ...
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager(); // ... 2
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    // Normally the ViewRoot sets up callbacks with the Activity
                    // in addView->ViewRootImpl#setView. If we are instead reusing
                    // the decor view we have to notify the view root that the
                    // callbacks may have changed.
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l); // ... 3
                    } else {
                        a.onWindowAttributesChanged(l);
                    }
                }
                ...
            } 
        } 
        ...
    }
       注释1 处的 performR es umeActi vity 方法 最终会调用 Acti vity 的 on Resume 方法 。在注释2处得到 View Manager 类型的 wm 对象 ,在注释3 处调用了 View Manager 的  add View 方法,而 add View 方法 则是在 WindowManagerlmpl 中实现的,此后的过程在上面的系统窗口 StatusBar 的添加过程中已经 讲过,唯 一需 要注意的是 View Manager 的  add View 方法 的第一个 参数为 Decor View ,这说明 Acitivty 窗口中会包含  Decor View。

     4.3 Window 的更新过程

       Window 的更新过程和 Window 的添加过程是类 似的 。需要调用 View Manager 的  update View Layout 方法 update View Layout 方法 Window Manag er Impl 中 实现, Window Manager Imp 的  up date View Layout 方法 会调用 WindowManagerGlobal 的  update View Layout 方法,代码如下 所示:
 
frameworks/base/core/java/android/view/WindowManagerGlobal.java

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

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

        view.setLayoutParams(wparams); // ... 1

        synchronized (mLock) {
            int index = findViewLocked(view, true); // ... 2
            ViewRootImpl root = mRoots.get(index); // ... 3
            mParams.remove(index);  // ... 4
            mParams.add(index, wparams); // ... 5
            root.setLayoutParams(wparams, false); // ... 6
        }
    }
       注释1 处将更新的参数设置到 View 中。注释2 处得到要更新的窗口在 View 列表中的索引,注释3处在 ViewRootlmpl 列表中根据索引得到窗口的 ViewRootlmpl ,注释4 和注释5 处用于更新布局参数列表,注释6处调用 ViewRootlmpl 的  setLayoutParams 方法 将更新的 参数设置到 ViewRootlmpl 中。 ViewRootlmpl 的  setLayoutParams 方法 在最后会调用 ViewRootlmpl 的 schedule Traversals 方法 ,如下所示:
 
frameworks/base/core/java/android/view/ViewRootlmpl.java
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); // ... 1
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
       注释1 处的 Choreographer 用于接收显示系统的 VSync 信号,在下一 个帧渲染时控制执行 一些操作。 C horeographer 的 postCallback 方法 用于发起添加回调, 这个添加的回调将在下一帧被渲染时执行。这个添加的回调指的是注释1处的 TraversalRunnable 类型的 mTraversalRunnable ,如下所示:
 

frameworks/base/core/java/android/view/ViewRootlmpl.java

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

       在 TraversalRunnable 的 run 方法中调用了 doTraversal 方法,如下所示:

frameworks/base/core/java/android/view/ViewRootlmpl.java

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

       在doTraversal 方法中又调用了 performTraversals 方法performTraversals 方法使得 ViewTree 开始 View 的工作流程,如下所示:

frameworks/base/core/java/android/view/ViewRootlmpl.java

  private void performTraversals() {
        ...
        int relayoutResult = 0;
        ...
        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            mForceNextWindowRelayout = false;
            ...
            try { 
                ...
                relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); // ... 1
                ...
            } catch (RemoteException e) {
            }

            ...

            if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                    ...

                     // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // ... 2
                    ...
                }
            }
        } 
        ...
        if (didLayout) {
            performLayout(lp, mWidth, mHeight); // ... 3
            ...
        }
        ...
        if (!cancelDraw && !newSurface) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }

            performDraw(); // ... 4
        } else {
           ...
        }

        mIsInTraversal = false;
    }
       注释1 处的 relayoutWi ndow 方法 内部会调用 IWindowSession 的  relayout 方法 来更新 Window 视图 ,最终会调 WMS 的 relayoutWindow 方法。除此之外, perform Traversals 方法 还会在注释 2, 3, 4 处分别调用 performMeasure 、performLayout 、 performDraw 方法,它们的内部又会调用 View 的  measure 、 layou 、 draw 方法 ,这样就完成了 V iew 的工作流程 。在 perform Traversals 方法中更新了 Window 视图,又执行 Window 中的 View 的工作流程,这样就完成了 Window 的更新。

猜你喜欢

转载自blog.csdn.net/lixiong0713/article/details/107279903