【Android关键流程】Window相关类及属性

文章目录

  • [WMS 及其成员](#WMS 及其成员)
  • [Window 相关类](#Window 相关类)
    • [Window、WindowManager 和 WMS 的关系](#Window、WindowManager 和 WMS 的关系)
    • [WindowManager 相关方法](#WindowManager 相关方法)
    • [PhoneWindow 相关方法](#PhoneWindow 相关方法)
  • [Window 的属性](#Window 的属性)
    • [Window 的类型和显示次序](#Window 的类型和显示次序)
    • [Window 的标志](#Window 的标志)
    • 软键盘相关模式

基于Android U

WMS 及其成员

WMS 的职责

  1. 窗口管理

WMS 是窗口的管理者,它负责窗口的启动、添加和删除,另外窗口的大小和层级也是由 WMS 进行管理的。窗口管理的核心成员有 DisplayContent、WindowToken 和 WindowState。

  1. 窗口动画

窗口间进行切换时,使用窗口动画可以显得更炫一些,窗口动画由WMS的动画子系统来负责,动画子系统的管理者为 WindowAnimator。

  1. 输入系统的中转站

通过对窗口的触摸从而产生触摸事件,InputManagerService(IMS)会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS 是窗口的管理者,它作为输入系统的中转站再合适不过了。

  1. Surface 管理

窗口并不具备绘制的功能,因此每个窗口都需要有一块 Surface 来供自己绘制,为每个窗口分配 Surface 是由 WMS 来完成的。

WMS的重要成员

  • mPolicy: WindowManagerPolicy

mPolicy 是 WindowManagerPolicy(WMP)类型的变量。WindowManagerPolicy 是窗口管理策略的接口类,用来定义一个窗口策略所要遵循的通用规范,并提供了 WindowManager 所有的特定的 UI 行为。它的具体实现类为 PhoneWindowManager,这个实现类在 WMS 创建时被创建。WMP 允许定制窗口层级和特殊窗口类型以及关键的调度和布局。

  • mSessions: ArraySet

mSessions 是 ArraySet 类型的变量,元素类型为 Session,它主要用于进程间通信,其他的应用程序进程想要和 WMS 进程进行通信就需要经过 Session,并且每个应用程序进程都会对应一个 Session,WMS 保存这些 Session 用来记录所有向 WMS 提出窗口管理服务的客户端。

  • mWindowMap: HashMap

mWindowMap 是 HashMap 类型的变量,用来保存 WMS 中各种窗口的集合。key 值的类型为 IBinder,value 值的类型为 WindowState。WindowState 用于保存窗口的信息,在 WMS 中它用来描述一个窗口。

  • mResizingWindows: ArrayList

mResizingWindows 是 ArrayList 类型的变量,元素类型为 WindowState。mResizingWindows 是用来存储正在调整大小的窗口的列表。与 mResizingWindows 类型的还有 mForceRemoves、mDestroySurface 等,其中mForceRemoves 是在内存耗尽时设置的,里面存有需要强制删除的窗口,mDestroySurface 里面存有需要被销毁的 Surface。

  • mAnimator: WindowAnimator

mAnimator 是 WindowAnimator 类型的变量,用于管理窗口的动画以及特效动画。

  • mH: H

mH 是 H 类型的变量,系统的 Handler 类,用于将任务加入到主线程的消息队列中,这样代码逻辑就会在主线程中执行。

  • mInputManager: InputManagerService

mInputManager 是 InputManagerService 类型的变量,输入系统的管理者。InputManagerService(IMS)会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS 是窗口的管理者,因此 WMS 作为输入系统的中转站是再合适不过了。

Window 相关类

Window、WindowManager 和 WMS 的关系

Window 是一个抽象类,具体的实现类为 PhoneWindow,它对 View 进行管理。

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

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

WindowManager 相关方法

WindowManager 是一个接口类,继承自接口 ViewManager,ViewManager 中定义了三个方法,分别用来添加、更新和删除 View。

java 复制代码
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);
}

WindowManager 也继承了这些方法,而这些方法传入的参数都是 View 类型,说明 Window 是以 View 的形式存在的。WindowManager 在继承 ViewManager 的同时,又加入很多功能,包括 Window 的类型和层级相关的常量、内部类以及一些方法,其中有两个方法是根据 Window 的特性加入的:

java 复制代码
frameworks/base/core/java/android/view/WindowManager.java
public Display getDefaultDisplay();
public void removeViewImmediate(View view);

getDefaultDisplay() 方法能够得知这个 WindowManager 实例将 Window 添加到哪个屏幕上了,换句话说,就是得到 WindowManager 所管理的屏幕(Display)。

removeViewImmediate() 方法则规定在这个方法返回前要立即执行 View.onDetachedFromWindow(),来完成传入的 View 相关的销毁工作。

PhoneWindow 相关方法

PhoneWindow 是在 Activity 创建的 attach() 方法中创建的。

java 复制代码
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, IBinder assistToken,
        IBinder shareableActivityToken) {
    ...
    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
    ...
}

注释1处创建了 PhoneWindow,注释2处调用 PhoneWindow#setWindowManager() 方法,这个方法在 PhoneWindow 的父类 Window 中实现。

java 复制代码
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;
    if (wm == null) {
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);  // 1
    }
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);  // 2
}
  1. 如果传入的 WindowManager 为 null,就会在注释1处调用 Context#getSystemService() 方法,并传入服务的名称 Context。WINDOW_SERVICE(值为 window),具体在 ContextImpl 中实现。

    java 复制代码
    frameworks/base/core/java/android/app/ContextImpl.java
    public Object getSystemService(String name) {
        ...
        return SystemServiceRegistry.getSystemService(this, name);
    }

    调用 SystemServiceRegistry#getSystemService() 方法。

    java 复制代码
    frameworks/base/core/java/android/app/SystemServiceRegistry.java
    public static Object getSystemService(ContextImpl ctx, String name) {
        ...
        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        ...
        final Object ret = fetcher.getService(ctx);
        ...
        return ret;
    }

    SYSTEM_SERVICE_FETCHERS 是一个 ArrayMap,其 Key 为系统服务名,Value 为 ServiceFetcher 对象。

    • SYSTEM_SERVICE_FETCHERS 是在什么时候被赋值的?

      java 复制代码
      frameworks/base/core/java/android/app/SystemServiceRegistry.java
      private static <T> void registerService(@NonNull String serviceName,
              @NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
          SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
          SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
          SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
      }
    • registerService() 方法是什么时候被调用的?

      java 复制代码
      frameworks/base/core/java/android/app/SystemServiceRegistry.java
      static {
          ...
          registerService(Context.WINDOW_SERVICE, WindowManager.class,
                  new CachedServiceFetcher<WindowManager>() {
              @Override
              public WindowManager createService(ContextImpl ctx) {
                  return new WindowManagerImpl(ctx);  // 1
              }});
          ...
      }

      在 SystemServiceRegistry 的静态代码块中会调用多个 registerService() 方法,registerService() 方法内部会将传入的服务的名称存入到 SYSTEM_SERVICE_FETCHERS 中。从注释1处可以看出,传入的 Context.WINDOW_SERVICE 对应的就是 WindowManagerImpl 实例。

  2. mContext.getSystemService() 得到 WindowManagerImpl 实例后转为 WindowManager 类型,在注释2处调用了 WindowManagerImpl#createLocalWindowManager() 方法。

    java 复制代码
    frameworks/base/core/java/android/view/WindowManagerImpl.java
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow, mWindowContextToken);
    }

    createLocalWindowManager() 方法同样也是创建 WindowManagerImpl,不同的是这次创建 WindowManagerImpl 时将创建它的 Window 作为参数传了进来,这样 WindowManagerImpl 就持有了 Window 的引用,可以对 Window 进行操作,比如在 Window 中添加 View,会调用 WindowManagerImpl 的 addView() 方法,如下所示:

    java 复制代码
    frameworks/base/core/java/android/view/WindowManagerImpl.java
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        android.util.SeempLog.record_vg_layout(383,params);
        applyTokens(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());  // 1
    }

    注释1处调用了 WindowManagerGlobal 的 addView() 方法,其中参数 mParentWindow 就是上面提到的 Window,可以看出 WindowManagerImpl 虽然是 WindowManage 的实现类,但是没有实现什么功能,而是将功能实现委托给了 WindowManagerGlobal,这里用到的是桥接模式。

我们来查看 WindowManagerImpl 中是如何定义 WindowManagerGlobal 的。

java 复制代码
public final class WindowManagerImpl implements WindowManager {
    @UnsupportedAppUsage
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();  // 1
    @UiContext
    @VisibleForTesting
    public final Context mContext;
    private final Window mParentWindow;  // 2
    
    private WindowManagerImpl(Context context, Window parentWindow,
            @Nullable IBinder windowContextToken) {
        mContext = context;
        mParentWindow = parentWindow;  // 3
        mWindowContextToken = windowContextToken;
        mWindowMetricsController = new WindowMetricsController(mContext);
    }
}

注释1处可以看出 WindowManagerGlobal 是一个单例,说明在一个进程中只有一个 WindowManagerGlobal 实例。

注释2处结合注释3处说明这个 WindowManagerImpl 实例会作为哪个 Window 的子 Window,这也就说明在一个进程中 WindowManagerImpl 可能会有多个实例。

通过如上的源码分析,WindowManager 的关联类如下图所示:

从图中可以看出,PhoneWindow 继承自 Window,Window 通过 setWindowManager() 方法与 WindowManager 发生关联。WindowManager 继承自接口 ViewManager,WindowManagerImpl 是 WindowManager 接口的实现类,但是具体的功能都会委托给 WindowManagerGlobal 来实现。

Window 的属性

WMS 是 Window 的最终管理者,Window 好比是员工,WMS 是老板,为了方便老板管理员工则需要定义一些"协议",这些"协议"就是 Window 的属性,它们被定义在 WindowManager 的内部类 LayoutParams 中,了解 Window 的属性能够更好地理解 WMS 的内部原理。Window 的属性有很多种,与应用开发最密切的有三种,分别是 Type(Window 的类型)、Flag(Window 的标志)和 SoftInputMode(软键盘相关模式),下面分别介绍这三种 Window 的属性。

Window 的类型和显示次序

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

  1. 应用程序窗口

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

java 复制代码
// 应用程序窗口类型初始值
public static final int FIRST_APPLICATION_WINDOW = 1;
// 窗口的基础值,其他的窗口值要大于这个值
public static final int TYPE_BASE_APPLICATION   = 1;
// 普通的应用程序窗口类型
public static final int TYPE_APPLICATION        = 2;
// 应用程序启动窗口类型,用于系统在应用程序窗口启动前显示的窗口
public static final int TYPE_APPLICATION_STARTING = 3;
public static final int TYPE_DRAWN_APPLICATION = 4;
// 应用程序窗口类型结束值
public static final int LAST_APPLICATION_WINDOW = 99;

应用程序窗口包含了以上几种 Type 值,应用程序窗口的 Type 值范围为1~99,这个数值的大小涉及窗口的层级。

  1. 子窗口

子窗口,顾名思义,它不能独立存在,需要附着在其他窗口才可以, PopupWindow 就属于子窗口。子窗口的类型定义如下所示:

java 复制代码
// 子窗口类型初始值
public static final int FIRST_SUB_WINDOW = 1000;
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
// 子窗口类型结束值
public static final int LAST_SUB_WINDOW = 1999;

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

  1. 系统窗口

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

java 复制代码
// 系统窗口类型初始值
public static final int FIRST_SYSTEM_WINDOW     = 2000;
public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;
public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;
...
// 系统窗口类型结束值
public static final int LAST_SYSTEM_WINDOW      = 2999;

这里只列出部分系统窗口的类型值,系统窗口的 Type 值范围为2000~2999。

  1. 窗口显示次序

当一个进程向 WMS 申请一个窗口时,WMS 会为窗口确定显示次序。为了方便窗口显示次序的管理,手机屏幕可以虚拟地用 X、Y、Z 轴来表示,其中 Z 轴垂直于屏幕,从屏幕内指向屏幕外,这样确定窗口显示次序也就是确定窗口在 Z 轴上的次序,这个次序称为 Z-Oder。Type 值是 Z-Oder 排序的依据,我们知道应用程序窗口的 Type 值范围为1~99,子窗口1000~1999,系统窗口2000~2999,在一般情况下,Type 值越大则 Z-Oder 排序越靠前,就越靠近用户。当多个窗口的 Type 值相同,WMS 会结合各种情况给出最终的 Z-Oder。

Window 的标志

Window 的标志也就是 Flag,用于控制 Window 的显示,同样被定义在 WindowManager 的内部类 LayoutParams 中。这里列出几个常用的:

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_NO_LIMITS 允许窗口超过屏幕之外
FLAG_FULLSCREEN 隐藏所有的屏幕装饰窗口,比如在游戏、播放器中的全屏显示
FLAG_SHOW_WHEN_LOCKED 窗口可以在锁屏的窗口之上显示
FLAG_IGNORE_CHEEK_PRESSES 当用户的脸贴近屏幕时(比如打电话),不会去响应此事件
FLAG_TURN_SCREEN_ON 窗口显示时将屏幕点亮

设置 Window 的 Flag 有三种方法。

  1. 通过 Window 的 addFlags() 方法。

    java 复制代码
    Window mWindow = getWindow();
    mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
  2. 通过 Window 的 setFlags() 方法。

    java 复制代码
    Window mWindow = getWindow();
    mWindow.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN);

    其实 Window 的 addFlags() 方法内部会调用 setFlags() 方法,因此这两种方法区别不大。

  3. 给 LayoutParams 设置 Flag,并通过 WindowManager 的 addView() 方法进行添加。

    java 复制代码
    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);

软键盘相关模式

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

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 当软键盘弹出时,窗口不需要调整大小,要确保输入焦点是可见的

从上面给出的 SoftInputMode,可以发现,它们与 AndroidManifest 中 Activity 的属性 android:windowSoftInputMode 是对应的。因此除了在AndroidManifest中为 Activity 设置 android:windowSoftInputMode 以外,还可以在 Java 代码中为 Window 设置 SoftInputMode,如下所示:

java 复制代码
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
相关推荐
我命由我123451 小时前
Android多进程开发 - AIDL 最简单的实现、传递数据大小限制
android·java·java-ee·kotlin·android studio·android jetpack·android-studio
冬奇Lab9 小时前
Android系统启动流程深度解析:从Bootloader到Zygote的完整旅程
android·源码阅读
泓博10 小时前
Android中仿照View selector自定义Compose Button
android·vue.js·elementui
zhangphil11 小时前
Android性能分析中trace上到的postAndWait
android
十里-12 小时前
vue2的web项目打包成安卓apk包
android·前端
p***199412 小时前
MySQL——内置函数
android·数据库·mysql
兆子龙13 小时前
我成了🤡, 因为不想看广告,花了40美元自己写了个鸡肋挂机脚本
android·javascript
儿歌八万首14 小时前
Android 全局监听神器:registerActivityLifecycleCallbacks 解析
android·kotlin·activity
弹幕教练宇宙起源15 小时前
cmake文件介绍及用法
android·linux·c++