理解 Window 和 WindowManager

Window 是一个抽象类,它的具体实现是 PhoneWindow。可以通过 WindowManager 创建 Window。Android 中所有视图都是附加在 Window 上的,因此 Window 实际上是 View 的直接管理者。

Window 和 WindowManager

先看一段示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
WindowManager wmManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); 

WindowManager.LayoutParams wmParams = new WindowManager.LayoutParams();

wmParams.type = LayoutParams.TYPE_PHONE;

wmParams.format = PixelFormat.RGBA_8888;

wmParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCHABLE;

wmParams.gravity = Gravity.RIGHT | Gravity. CENTER_VERTICAL;

wmParams.x = 0;
wmParams.y = 0;

wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

wmManager.addView(view,wmParams);

上面的代码简单的实现了一个悬浮窗的功能,里面涉及到了以下知识点:

  1. Flags 常用选项:

    • FLAG_NOT_FOCUSABLE: 表示 Window 不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用 FLAG_NOT_TOUCH_MODAL,最终事件会往下传递。
    • FLAG_NOT_TOUCH_MODAL:表示会将当前 Window 区域以外的点击事件传递给底层的 Window,自己区域内的当然由自己来处理,一般来说,都要开启本标记。
    • FLAG_SHOW_WHEN_LOCKED:表示可以让 Window 显示在锁屏界面上。
  2. Types 参数:
    Types 参数表示 Window 的类型,有三种:应用 Window子 Window系统 Window

    • 应用 Window:对应着一个 Activity
    • 子 Window:不能单独存在,它需要附属在特定的父 Window 上,例如 Dialog
    • 系统 Window:需要申明权限才能创建,例如 Toast 和状态栏
  3. Window 是分层的,每个 Window 都有对应的 z-ordered,层级大的会覆盖在层级小的上面。这些层级范围对应着上面的 Types 参数。

    • 应用 Window 层级范围:1~99
    • 子 Window 层级范围:1000~1999
    • 系统 Window 层级范围:2000~2999
  4. 创建系统类型的 Window 时需要声明权限 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />,不然会报错。

  5. WindowManager 提供的功能很简单,常用的就三个:添加 View更新 View删除 View
  6. 如果我们要实现悬浮窗的拖动只要在 onTouch 方法中不断更新 View 的位置即可。

Window 的内部机制

Window 是一个抽象的概念,每一个 Window 都对应着一个 View 和一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系,因此 Window 并不实际存在,它以 View 的形式存在,这就是为什么实际使用中无法直接访问 Window,而都是通过 WindowManager 来进行的。

Window 的添加过程

Window 的添加过程需要通过 WindowManager 的 addView 来实现。WindowManager 是一个接口,它的真正实现是 WindowManagerImpl 类,翻看源码发现 WindowManagerImpl 也没有直接实现 Window 的三大操作,而是委托给了 WindowManagerGlobal 来实现。

WindowManagerGlobal 的 addView 方法主要分为几步:

  1. 检查参数是否合法,如果是子 Window 那么还需要调整一些布局参数
  2. 创建 ViewRootImpl 并将 View 添加到列表中
  3. 通过 ViewRootImpl 来更新界面并完成 Window 的添加过程

最终会通过 IPC 调用 WindowManagerService 去处理。

Window 的删除过程

WindowManager 提供两种删除接口: removeViewremoveViewImmediate,我们一般使用前者,后者可能会出现一些意外的错误。

真正删除 View 的逻辑在 dispatchDetachedFromWindow 方法内部实现,主要做四件事:

  1. 垃圾回收相关的工作
  2. 通过 Session 的 remove 方法删除 Window,同样是一个 IPC 过程,最终调用 WindowManagerService 的 removeWindow 方法
  3. 调用 View 的 dispatchDetachedFromWindow 方法
  4. 调用 WindowManagerGlobal 的 doRemoveView 方法刷新数据。
Window 的更新过程

Window 的更新主要是通过 WindowManagerGlobal 的 updateViewLayout 方法,首先更新 View 的 LayoutParams,接着更新 ViewRootImpl 中的 LayoutParams,在 ViewRootImpl 中会通过 scheduleTraversals 方法来对 View 进行重新布局,此外还会通过 WindowSession 来更新 Window 的视图,最终由 WindowManagerService 的 relayoutWindow 来实现。

Window 的创建过程

Activity 的 Window 创建过程

Activity 所属的 Window 对象通过 PolicyManager 的 makeNewWindow 方法实现。Window 的具体实现是 PhoneWindow。Activity 通过 setContentView 方法把具体实现交给了 PhoneWindow 去处理。

PhoneWindow 的 setContentView 大致有以下几个步骤:

  1. 如果没有 DecorView,那么创建它

    DecorView 是一个 FrameLayout,是 Activity 中的顶级 View,它包含标题栏和内容栏,其实内容栏的完整 id 是 android.R.id.content。

  2. 将 View 添加到 DecorView 的 mContentParent 中

  3. 回调 Activity 的 onContentChanged 方法通知 Activity 的视图已经发生改变

最后在 ActivityThead 的 handleResumeActivity 方法中调用 Activity 的 onResume 方法,接着会调用 Activity 的 makeVisible 方法,最终才能显示出来。

Dialog 的 Window 创建过程

Dialog 的 Window 创建过程与 Activity 类似,具体有以下几个步骤:

  1. 创建 Window
  2. 初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中
  3. 将 DecorView 添加到 Window 中并显示

值得注意的是,普通 Dialog 必须采用 Activity 的 Context,如果是 Application 的则会报错,这是因为没有 token 所导致的,一般只有 Activity 才拥有。

此外,系统 Window 是不需要 token 的,但是需要声明权限。

Toast 的 Window 创建过程

Toast 属于系统 Window,它的内部视图由两种方式指定:一是系统默认样式,二是通过 setView 方法来指定一个自定义 View。Toast 的内部有两类 IPC 过程,第一类是 Toast 访问 NotificationManagerService,第二类是 NotificationManagerService 回调 Toast 里的 TN 接口,最终实现 Toast 的显示和隐藏。