11、WindowManagerService 核心架构:WMS 的启动、职责与关键成员变量
好,我们进入 WMS 的世界。说实话,WindowManagerService 是整个 Android 窗口系统的“大脑”。你写的每个 Activity、每个 Dialog、每个 Toast,最终都要经过它来安排位置、决定层级、处理动画。我当年刚接触 WMS 时,觉得它就是个黑盒子,直到啃了源码才明白——嗯,其实它的核心逻辑并不复杂,复杂的是各种边界情况。
11.1 WMS 的启动时机
WMS 是在 SystemServer 进程里启动的。SystemServer 是 Android 的“大管家”,它负责启动各种系统服务。WMS 的启动顺序很讲究——它必须在 InputManagerService 和 DisplayManagerService 之后启动,因为 WMS 依赖它们提供输入事件和显示信息。
启动代码在 SystemServer.java 的 startOtherServices() 方法里。我截取关键部分:
// SystemServer.java
private void startOtherServices() {
// 先启动 DisplayManagerService
DisplayManagerService dms = new DisplayManagerService(context);
ServiceManager.addService(Context.DISPLAY_SERVICE, dms);
// 再启动 InputManagerService
InputManagerService ims = new InputManagerService(context);
ServiceManager.addService(Context.INPUT_SERVICE, ims);
// 最后启动 WindowManagerService
WindowManagerService wms = WindowManagerService.main(context, ims,
dms.getDisplayManager(), policy, factoryTest);
ServiceManager.addService(Context.WINDOW_SERVICE, wms);
}
注意 WindowManagerService.main() 是个静态方法,它内部会创建 WMS 实例并初始化。我建议你记住这个调用链:main() → 构造方法 → init()。构造方法里做了大量初始化工作,包括创建 WindowAnimator、InputMonitor、DisplayContent 等核心组件。
核心要点:WMS 启动依赖 IMS 和 DMS,三者顺序不能乱。如果调换顺序,WMS 初始化时会因为拿不到输入通道或显示信息而崩溃。
11.2 WMS 的核心职责
WMS 到底管什么?说白了就三件事:
- 窗口管理:添加、删除、调整窗口。每个窗口的尺寸、位置、层级都由 WMS 决定。
- 输入事件分发:WMS 告诉 InputManagerService:“这个触摸事件应该发给哪个窗口”。它维护了一个窗口的 Z-order 列表,事件按层级从上往下传递。
- 动画与过渡:窗口打开、关闭、切换时的动画效果,由 WMS 协调 WindowAnimator 执行。
我在项目中遇到过一个问题:某个悬浮窗总是收不到触摸事件。排查了半天,发现是 WMS 里窗口的 InputChannel 没有正确注册。嗯,这其实是个很典型的坑——WMS 负责把窗口的输入通道注册到 IMS 里,如果注册失败,窗口就“聋”了。
11.3 关键成员变量
WMS 的成员变量很多,但核心的就那么几个。我按功能分类给你列出来:
| 变量名 | 类型 | 作用 |
|---|---|---|
mWindowMap |
WindowHashMap | 存储所有窗口,key 是 IBinder,value 是 WindowState |
mDisplayContents |
ArrayList<DisplayContent> | 管理每个显示屏上的窗口层级 |
mInputMonitor |
InputMonitor | 负责窗口与输入事件的关联 |
mAnimator |
WindowAnimator | 处理窗口动画 |
mPolicy |
WindowManagerPolicy | 窗口策略接口,决定窗口行为 |
mRoot |
RootWindowContainer | 窗口树的根节点,管理所有窗口层级 |
我来逐个解释一下这些变量的实际用途。
mWindowMap:窗口的“户口本”
这个 HashMap 记录了所有活着的窗口。每个窗口对应一个 WindowState 对象。WindowState 包含了窗口的所有信息:尺寸、位置、层级、可见性、输入通道等。我调试问题时,经常从这里 dump 出所有窗口的状态。
mDisplayContents:多屏管理的基石
Android 支持多屏显示(比如手机投屏到电视)。每个 DisplayContent 对应一个逻辑显示屏,它内部维护了一个窗口层级列表。你想想看,如果两个屏幕上的窗口互相干扰,那不乱套了?WMS 通过 DisplayContent 把它们隔离开。
mInputMonitor:窗口与输入的桥梁
这个变量很关键。它负责把窗口的 InputChannel 注册到 InputManagerService。每次窗口添加或移除,mInputMonitor 都会更新输入系统的窗口列表。我曾经遇到一个 bug:窗口关闭后,触摸事件仍然能穿透到它原来的位置。查到最后发现是 mInputMonitor 没有及时移除已销毁窗口的输入通道。
mAnimator:动画的调度者
窗口动画不是随便乱跑的。mAnimator 负责协调所有窗口的动画帧,它会在每个 VSync 信号到来时计算动画进度,然后更新窗口位置和透明度。我建议你记住:WindowAnimator 内部维护了一个 Choreographer,用来同步帧率。
避坑指南:我曾经在自定义窗口动画时,直接修改了 WindowState 的位置属性,结果动画和实际位置不同步。正确的做法是通过 mAnimator 的动画回调来更新,而不是直接改 WindowState。
11.4 WMS 的核心架构图
下面这张图展示了 WMS 的内部结构以及它与其他服务的关系。我画的时候特意把关键成员变量和它们之间的数据流标出来了。
11.5 启动流程中的关键细节
WMS 启动时,有几个细节我建议你特别留意:
- 策略初始化:
mPolicy是PhoneWindowManager的实例,它定义了窗口的各种行为规则,比如状态栏高度、导航栏位置、按键处理等。这个策略类是在 WMS 构造方法里通过policy.makeDefaultWidthResource()初始化的。 - RootWindowContainer 的创建:
mRoot是窗口树的根节点,所有 DisplayContent 都挂在这个树上。它负责遍历所有窗口,执行布局、绘制、输入分发等操作。 - DisplayContent 的注册:WMS 启动时会为默认显示屏(通常是手机屏幕)创建一个 DisplayContent,并注册到 mDisplayContents 中。后续如果有外接显示器,会动态添加。
注意:WMS 启动过程中,如果 DisplayManagerService 还没有准备好,WMS 会阻塞等待。这就是为什么启动顺序必须严格遵守。我在 Android 8.0 上遇到过因为 DMS 启动延迟导致 WMS 卡死的问题,最后加了超时机制才解决。
11.6 我的一点经验
说实话,WMS 的代码量很大(超过 2 万行),但核心架构其实很清晰。我个人习惯是:先抓住 mWindowMap 和 mDisplayContents 这两个数据结构,然后理解 addWindow() 和 removeWindow() 这两个入口方法。只要搞懂了窗口怎么加进来、怎么移出去,剩下的动画、输入、策略都是围绕这两个操作展开的。
嗯,这一章的内容就到这里。记住 WMS 的启动顺序、核心职责和那几个关键成员变量,后面讲窗口添加流程时你会觉得轻松很多。
公众号:蓝海资料掘金营,微信deep3321