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 开始对悬浮窗权限管控越来越严,大家要注意。
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 都没了,窗口怎么可能显示出来?
- 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 值。
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 的创建需要等到窗口第一次布局时才能确定具体尺寸。
- 权限检查:验证调用方是否有权限添加指定类型的窗口
- Token 验证:确保窗口绑定了一个有效的 Token
- 创建 WindowState:封装窗口的所有状态信息
- 窗口排序:根据类型和 Z-order 插入到窗口列表的正确位置
- 创建 SurfaceControl:为窗口分配 Surface 资源
- 返回结果:成功返回 0,失败返回对应的错误码
这张图把 addWindow() 的五个步骤串起来了。你仔细看,每一步都有对应的错误码。我在调试窗口问题时,最喜欢看的就是 WMS 返回的错误码。它就像侦探破案时的线索,能帮你快速定位问题。
最后说一句,addWindow() 只是窗口生命周期的开始。窗口显示出来后,还要经历 relayout、draw、commit 等阶段。但那是后面章节的内容了。今天先把 addWindow() 吃透,后面的路就好走了。
公众号:蓝海资料掘金营,微信 deep3321