16、AMS 与 WMS 的交互:Activity窗口添加、焦点切换、屏幕旋转流程
各位同学,今天我们来聊聊 AMS 和 WMS 这对“黄金搭档”。
说实话,刚接触 Android 框架时,我总觉得 AMS 和 WMS 是各管各的。AMS 管 Activity 生命周期,WMS 管窗口显示,井水不犯河水。但后来我踩了个坑——有一次调试一个启动白屏问题,发现 Activity 已经 onResume 了,窗口却还没创建出来。这才意识到,这两者之间的交互,才是真正决定用户体验的关键。
16.1 窗口添加:一场精心编排的“双人舞”
当一个 Activity 启动时,AMS 和 WMS 是怎么配合的?
我习惯把整个过程拆成三个阶段:
- AMS 发起请求:Activity 启动后,AMS 通过
WindowManagerService.addWindow()告诉 WMS:“我要显示一个窗口。” - WMS 分配 Surface:WMS 创建
WindowState,分配SurfaceControl,并计算窗口布局。 - SurfaceFlinger 合成:最终由 SurfaceFlinger 把图层合成并显示到屏幕上。
嗯,这里要注意一个关键点:AMS 不会直接调用 WMS。它们之间通过 WindowManagerService 的 Binder 接口通信。AMS 持有 WMS 的引用,但调用时是跨进程的。
核心流程代码片段(简化版):
// AMS 端
void startActivityLocked(ActivityRecord r) {
// ... 生命周期处理 ...
// 通知 WMS 添加窗口
WindowManagerService wms = getWindowManager();
wms.addWindow(r, r.appToken, ...);
}
// WMS 端
int addWindow(WindowManager.LayoutParams attrs, ...) {
WindowState win = new WindowState(this, ...);
// 分配 Surface
SurfaceControl surface = makeSurface(win, attrs);
win.setSurfaceControl(surface);
// 添加到窗口列表
mWindowMap.put(attrs.token, win);
// 触发布局
performLayoutAndPlaceSurfacesLocked();
return WindowManagerImpl.ADD_OKAY;
}
我曾经在项目中遇到过一个问题:第三方应用启动时,窗口添加成功了,但 Surface 一直没显示。后来发现是 performLayoutAndPlaceSurfacesLocked() 里有个死锁,导致布局线程卡住了。嗯,这种问题最难排查,因为日志里看不出任何异常。
16.2 焦点切换:谁说了算?
焦点切换,说白了就是“当前哪个窗口能接收按键事件”。
你可能会想,这还不简单?哪个 Activity 在最前面,焦点就给谁。但实际情况要复杂得多——比如输入法窗口、状态栏、弹窗,它们都可能抢焦点。
我个人习惯把焦点切换的规则总结为三点:
- AMS 决定“谁可以拥有焦点”:只有处于
RESUMED状态的 Activity 对应的窗口,才有资格获得焦点。 - WMS 决定“焦点给谁”:WMS 根据窗口的 Z-order、焦点标志(
FLAG_NOT_FOCUSABLE)等,从候选窗口中选出最终焦点窗口。 - InputDispatcher 执行:焦点确定后,WMS 通知 InputDispatcher,把事件路由到对应窗口。
避坑指南:
我曾经调试过一个 Bug:按 Home 键后,再点击应用图标,应用无法响应触摸事件。排查后发现,是某个 Dialog 在 dismiss 时没有正确释放焦点,导致 WMS 认为焦点还在 Dialog 上。解决方案是在 onDetachedFromWindow() 中显式调用 WindowManager.removeViewImmediate()。
焦点切换的时序,我画了一张图,大家可以看看:
16.3 屏幕旋转:一场“推倒重来”的博弈
屏幕旋转,可能是 AMS 和 WMS 交互中最“暴力”的场景了。
为什么这么说?因为旋转时,Activity 默认会重建(onDestroy() -> onCreate()),窗口也要重新创建。这背后涉及大量的状态同步。
我总结的旋转流程如下:
| 步骤 | 主导者 | 动作 |
|---|---|---|
| 1 | WMS | 检测屏幕方向变化,通知 AMS |
| 2 | AMS | 暂停当前 Activity,保存状态 |
| 3 | AMS | 重新创建 Activity(如果 configChanges 未处理) |
| 4 | WMS | 重新布局所有窗口,调整大小 |
| 5 | WMS | 通知 SurfaceFlinger 更新显示 |
注意:
如果 Activity 设置了 android:configChanges="orientation",AMS 不会重建 Activity,但 WMS 仍然会重新布局窗口。这时候如果应用没有正确处理 onConfigurationChanged(),就会出现界面错乱。
我曾经接手过一个项目,旋转屏幕后界面布局全乱了。排查发现,开发者在 onConfigurationChanged() 里只更新了部分 View,但 ConstraintLayout 的约束没有重新计算。解决方案是调用 requestLayout() 强制重新布局。
屏幕旋转时,AMS 和 WMS 的交互还有一个关键点:时序问题。
AMS 的重建操作和 WMS 的布局操作是并发的。如果 WMS 在 AMS 还没重建完窗口时就尝试布局,就会出现 WindowState 为 null 的情况。嗯,这里 WMS 做了保护:它会检查窗口是否已经就绪,如果没有,就延迟布局。
WMS 中的保护逻辑:
// WMS 布局时检查窗口状态
void performLayoutAndPlaceSurfacesLocked() {
for (WindowState win : mWindowMap.values()) {
if (win.mAppToken != null && !win.mAppToken.mAppDied) {
// 如果 Activity 正在重建,跳过布局
if (win.mAppToken.mActivityRecord == null ||
!win.mAppToken.mActivityRecord.isState(RESUMED)) {
continue;
}
}
// 正常布局
win.performLayout(...);
}
}
说实话,这个保护逻辑虽然解决了时序问题,但也引入了一个隐患:如果 Activity 重建卡住了,窗口布局就会一直延迟,导致屏幕旋转后长时间黑屏。我在项目中就遇到过这种情况——某个 Activity 的 onCreate() 里做了耗时操作,导致旋转后黑屏了 3 秒多。
16.4 总结:交互的本质是“状态同步”
回顾这三个场景,你会发现一个共同点:AMS 和 WMS 的交互,本质上是状态同步。
- 窗口添加:AMS 告诉 WMS“我要显示”,WMS 创建窗口并返回结果。
- 焦点切换:AMS 告诉 WMS“这个 Activity 激活了”,WMS 选择焦点窗口。
- 屏幕旋转:WMS 告诉 AMS“方向变了”,AMS 重建 Activity,WMS 重新布局。
我个人习惯把这种模式称为“事件驱动 + 状态校验”。AMS 和 WMS 各自维护自己的状态,通过 Binder 通信同步变化。任何一方的状态不一致,都会导致问题。
一个小建议:
如果你在调试 AMS 和 WMS 相关的问题,建议打开这两个日志:
adb logcat -s ActivityManager:看 AMS 的生命周期变化adb logcat -s WindowManager:看 WMS 的窗口操作
对照着看,能快速定位是哪个环节出了问题。
好了,关于 AMS 和 WMS 的交互,今天就聊到这里。记住,这两者的配合是 Android 窗口系统的基石,理解透了,很多疑难杂症都能迎刃而解。
公众号:蓝海资料掘金营,微信deep3321