14、WMS 添加窗口流程:addWindow() 方法的完整逻辑

各位同学,今天我们来啃一块硬骨头——WMS 的 addWindow() 方法。说实话,这个方法我翻来覆去看了不下二十遍。每次系统出问题,最后定位到这里的情况特别多。你想想看,一个窗口从应用层请求到真正显示在屏幕上,中间要过多少道关卡?addWindow() 就是那个总闸门。

我个人习惯把 addWindow() 的流程拆成四个阶段:权限检查 → Token 验证 → 窗口排序 → Surface 创建。咱们一个一个来。

14.1 权限检查:不是谁都能开窗户

先看第一道防线。WMS 收到 addWindow 请求后,第一件事就是检查调用方有没有资格。

// 源码位置:WindowManagerService.java
public int addWindow(Session session, IWindow client, ...) {
    // 权限检查
    final int callingUid = Binder.getCallingUid();
    final int callingPid = Binder.getCallingPid();
    
    // 检查是否拥有系统权限
    if (type >= FIRST_SYSTEM_WINDOW && type <= LAST_SYSTEM_WINDOW) {
        if (!checkCallingPermission(android.Manifest.permission.SYSTEM_ALERT_WINDOW)) {
            return WindowManagerGlobal.ADD_PERMISSION_DENIED;
        }
    }
    
    // 检查悬浮窗权限
    if (type == TYPE_APPLICATION_OVERLAY) {
        if (!checkCallingPermission(android.Manifest.permission.SYSTEM_ALERT_WINDOW)) {
            return WindowManagerGlobal.ADD_PERMISSION_DENIED;
        }
    }
}

这里有个坑,我曾经踩过。系统窗口类型(比如 TYPE_SYSTEM_ALERT)必须要有 SYSTEM_ALERT_WINDOW 权限。但很多第三方应用会偷偷申请这个权限,然后弹广告。嗯,Android 从 6.0 开始对悬浮窗权限管控越来越严,大家要注意。

⚠️ 避坑指南:我曾经遇到一个 case,某个应用在 Android 10 上无法添加悬浮窗,排查了半天发现是权限检查通过了,但后续的 token 验证失败了。所以权限检查只是第一关,别以为过了就万事大吉。

14.2 Token 验证:窗口的身份证

权限检查通过后,接下来是 Token 验证。Token 是什么?说白了就是窗口的身份证。每个窗口都必须绑定一个有效的 Token。

Token 分为两种:

  • 应用 Token(AppWindowToken):对应一个 Activity
  • 非应用 Token(WindowToken):对应系统窗口或子窗口
// Token 验证逻辑
if (token == null) {
    // 子窗口必须有父窗口的 token
    if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
        return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
    }
    // 系统窗口类型需要创建对应的 WindowToken
    if (type >= FIRST_SYSTEM_WINDOW) {
        token = new WindowToken(this, null, ...);
    }
} else {
    // 验证 token 是否有效
    if (!token.isValid()) {
        return WindowManagerGlobal.ADD_INVALID_DISPLAY;
    }
}

我记得有一次调试一个 Launcher 的窗口问题,发现它的 Token 莫名其妙被回收了。后来定位到是 Activity 销毁时没有正确清理 Token。你想想看,Token 都没了,窗口怎么可能显示出来?

💡 核心要点:Token 验证失败时,WMS 会返回对应的错误码。常见的错误码有:
  • ADD_BAD_APP_TOKEN:应用 Token 无效
  • ADD_BAD_SUBWINDOW_TOKEN:子窗口的父窗口 Token 无效
  • ADD_DUPLICATE_ADD:重复添加同一个窗口

14.3 窗口排序:谁在上面谁在下面

Token 验证通过后,WMS 要决定这个窗口放在哪一层。窗口排序是 WMS 最核心的逻辑之一,直接决定了界面的显示层级。

排序的依据是 WindowState 的 mBaseLayer 和 mSubLayer。mBaseLayer 由窗口类型决定,mSubLayer 用于子窗口的层级调整。

// 计算窗口层级
WindowState win = new WindowState(this, session, client, token, ...);
win.attach();

// 添加到窗口列表并排序
mWindowMap.put(client.asBinder(), win);
win.mWinAnimator.mSurfaceController = new SurfaceControl(...);

// 调整窗口顺序
assignLayersLocked(mWindows);

assignLayersLocked() 这个方法很有意思。它会遍历所有窗口,根据窗口类型和 Z-order 重新分配层级。我建议你去看一下源码,里面有个 for 循环,每次有新窗口加入都会重新计算所有窗口的 Z 值。

🔧 实战技巧:如果你需要调整窗口的显示层级,不要直接修改 Z-order。正确的做法是通过 WindowManager.LayoutParams 的 type 字段来指定窗口类型。系统窗口类型天然比应用窗口层级高。

14.4 Surface 创建:窗口的画布

最后一步,也是最关键的一步——创建 Surface。没有 Surface,窗口就是空中楼阁。

Surface 的创建涉及三个核心对象:

对象 职责 创建时机
SurfaceControl 管理 Surface 的生命周期 addWindow() 中创建
SurfaceSession 与 SurfaceFlinger 通信的会话 Session 创建时初始化
Surface 应用层操作的画布对象 relayoutWindow() 中返回
// Surface 创建的核心代码
SurfaceControl surfaceControl = null;
try {
    // 创建 SurfaceControl
    surfaceControl = new SurfaceControl.Builder(session)
            .setName(name)
            .setSize(w, h)
            .setFormat(format)
            .setFlags(flags)
            .setParent(windowContainer)
            .build();
    
    // 设置 Surface 的初始状态
    win.mWinAnimator.mSurfaceController = new WindowSurfaceController(surfaceControl, ...);
    win.mWinAnimator.mSurfaceController.setSize(w, h);
    
} catch (Exception e) {
    // 创建失败,清理资源
    if (surfaceControl != null) {
        surfaceControl.release();
    }
    return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}

这里有个细节要注意:Surface 的真正创建是在 relayoutWindow() 中完成的,addWindow() 只是创建了 SurfaceControl。为什么这么设计?我个人的理解是:addWindow() 负责窗口的注册和初始化,而 Surface 的创建需要等到窗口第一次布局时才能确定具体尺寸。

📌 完整流程总结:
  1. 权限检查:验证调用方是否有权限添加指定类型的窗口
  2. Token 验证:确保窗口绑定了一个有效的 Token
  3. 创建 WindowState:封装窗口的所有状态信息
  4. 窗口排序:根据类型和 Z-order 插入到窗口列表的正确位置
  5. 创建 SurfaceControl:为窗口分配 Surface 资源
  6. 返回结果:成功返回 0,失败返回对应的错误码
addWindow() 完整流程 ① 权限检查 ② Token 验证 ③ 创建 WindowState ④ 窗口排序 ⑤ 创建 SurfaceControl 常见错误码 ADD_PERMISSION_DENIED 权限不足 ADD_BAD_APP_TOKEN 应用 Token 无效 ADD_BAD_SUBWINDOW_TOKEN 子窗口 Token 无效 ADD_DUPLICATE_ADD 重复添加窗口 ADD_INVALID_DISPLAY 无效的 Display ADD_OKAY (0) 添加成功 核心数据结构 WindowState 窗口状态信息 WindowToken 窗口令牌 Session 客户端会话 SurfaceControl Surface 控制器 WindowList 窗口排序列表 DisplayContent 显示内容管理

这张图把 addWindow() 的五个步骤串起来了。你仔细看,每一步都有对应的错误码。我在调试窗口问题时,最喜欢看的就是 WMS 返回的错误码。它就像侦探破案时的线索,能帮你快速定位问题。

最后说一句,addWindow() 只是窗口生命周期的开始。窗口显示出来后,还要经历 relayout、draw、commit 等阶段。但那是后面章节的内容了。今天先把 addWindow() 吃透,后面的路就好走了。


公众号:蓝海资料掘金营,微信 deep3321