16、AMS 与 WMS 的交互:Activity窗口添加、焦点切换、屏幕旋转流程

各位同学,今天我们来聊聊 AMS 和 WMS 这对“黄金搭档”。

说实话,刚接触 Android 框架时,我总觉得 AMS 和 WMS 是各管各的。AMS 管 Activity 生命周期,WMS 管窗口显示,井水不犯河水。但后来我踩了个坑——有一次调试一个启动白屏问题,发现 Activity 已经 onResume 了,窗口却还没创建出来。这才意识到,这两者之间的交互,才是真正决定用户体验的关键。

16.1 窗口添加:一场精心编排的“双人舞”

当一个 Activity 启动时,AMS 和 WMS 是怎么配合的?

我习惯把整个过程拆成三个阶段:

  1. AMS 发起请求:Activity 启动后,AMS 通过 WindowManagerService.addWindow() 告诉 WMS:“我要显示一个窗口。”
  2. WMS 分配 Surface:WMS 创建 WindowState,分配 SurfaceControl,并计算窗口布局。
  3. 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()

焦点切换的时序,我画了一张图,大家可以看看:

焦点切换时序图 AMS WMS InputDispatcher 1. setFocusedApp() 2. 选择焦点窗口 3. setInputWindows() 4. 路由事件 5. 焦点切换完成回调

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