📚 故事背景:窗口管理局的日常
在上一集(WMS的诞生)中,我们见证了 窗口管理局(WMS) 的成立。现在,管理局开始正式运作了!它需要:
- 组建核心团队:招聘各部门主管(WMS 的重要成员变量)。
- 处理开新店申请 :当 App(店铺)想在屏幕(橱窗)上开新窗口(展示商品),需要向 WMS 提交申请(
addWindow
过程)。
这篇文章就是讲 WMS 的 核心团队构成 和 处理新窗口申请 的流程。
🧑💼 第一章:核心团队 - WMS 的重要成员
WMS 局长(WindowManagerService
实例)组建了自己的管理团队(成员变量)。这些成员各司其职:
swift
// WMS 局长办公室里的核心成员名单 (部分)
final WindowManagerPolicy mPolicy; // 策略顾问 (PhoneWindowManager)
final IActivityManager mActivityManager; // 工商局长 (AMS) 的联系方式
final AppOpsManager mAppOps; // 权限审查官
final ArraySet<Session> mSessions; // 所有已注册的"店铺代表"名单
final WindowHashMap mWindowMap; // 全局窗口登记簿 (窗口身份证 -> 窗口档案)
final ArrayList<AppWindowToken> mFinishedStarting; // 已开业的"旗舰店"名单
final H mH; // 内部通讯员 (Handler)
final WindowAnimator mAnimator; // 动画特效总监
final InputManagerService mInputManager; // 事件处理局 (IMS) 局长
-
mPolicy
(策略顾问 -PhoneWindowManager
):- 负责制定窗口管理的 根本规则:按键行为(Home/Back键)、状态栏/导航栏位置、多窗口模式(分屏、画中画)等。
- 比喻:城市规划局顾问,规定店铺橱窗能放哪、多大、什么样式。
-
mSessions
(店铺代表名单 -ArraySet<Session>
):- 每个 App(店铺)想和 WMS 打交道,必须先派一个常驻代表 (
Session
) 来注册。mSessions
记录了所有已注册的代表。 - 比喻:每个店铺在管理局登记一个联系人 (
Session
),以后办事都找他。
- 每个 App(店铺)想和 WMS 打交道,必须先派一个常驻代表 (
-
mWindowMap
(全局窗口登记簿 -WindowHashMap
):- 这是一个核心数据库 !键 (
Key
) 是窗口的唯一标识IBinder
(想象成窗口的身份证号),值 (Value
) 是WindowState
(窗口的详细档案)。 WindowState
是 WMS 内部描述一个窗口的核心数据结构,包含窗口大小、位置、层级、Surface 等所有信息。- 比喻:一本厚厚的登记簿,记录着城市里每一个橱窗 (窗口)的详细信息(
WindowState
)。
- 这是一个核心数据库 !键 (
-
mFinishedStarting
/mFinishedEarlyAnim
/...
(各种名单 -ArrayList<AppWindowToken>
):-
这些列表管理着代表应用程序窗口(主要是 Activity)的
AppWindowToken
。 -
AppWindowToken
是WindowToken
的子类,代表一个 Activity 的窗口令牌。 -
WindowToken
的核心作用:- 身份令牌 :App 想开新窗口(比如 Activity、Dialog),必须出示对应的
WindowToken
(证明你是合法店铺)。系统窗口(如状态栏)由 WMS 内部管理。 - 分组管理 :把属于同一个组件(如一个 Activity)的多个窗口(
WindowState
)归到一起,方便统一操作(比如关闭 Activity 时关掉所有它的窗口)。
- 身份令牌 :App 想开新窗口(比如 Activity、Dialog),必须出示对应的
-
比喻:
WindowToken
相当于一个 品牌授权书(比如"星巴克授权书")。有了它,才能开星巴克窗口(店铺)。AppWindowToken
是给 Activity 用的特殊授权书(旗舰店授权书)。mFinishedStarting
记录那些 已经完成开业手续 的旗舰店 (AppWindowToken
)。
-
-
mAnimator
(动画特效总监 -WindowAnimator
):- 负责管理和执行所有窗口的动画(打开、关闭、切换时的效果)。
- 比喻:橱窗特效师,负责让店铺切换展示时更炫酷。
-
mInputManager
(事件处理局长 -InputManagerService
):- 负责处理用户的触摸、按键事件。WMS 持有它的引用,因为事件最终要分发给正确的窗口(
WindowState
),而 WMS 知道所有窗口的位置和层级。 - 比喻:市民(用户)在橱窗上点击,事件局 (IMS) 需要问窗口管理局 (WMS):"这个位置点的是哪个橱窗?"
- 负责处理用户的触摸、按键事件。WMS 持有它的引用,因为事件最终要分发给正确的窗口(
-
mH
(内部通讯员 -Handler
):- 一个运行在
system_server
主线程上的Handler
。WMS 把需要在主线程执行的任务(比如更新 UI 状态)通过它发送。 - 比喻:局长的秘书,负责把重要通知传达给总部主办公楼 (
system_server
线程)。
- 一个运行在
📝 第二章:新窗口申请 - addWindow
流程详解
当 App(店铺)想开一个新橱窗(比如启动 Activity、弹出 Dialog),它会通过 WindowManager
向 WMS 提交申请:"我要加个窗口!" (addWindow()
)。
WMS 的 addWindow
方法是处理这个申请的核心,代码很长,但逻辑清晰,主要分 3 个阶段:
🔍 阶段 1:安全检查与准备 (addWindow
Part 1)
java
public int addWindow(Session session, IWindow client, ...) {
// 1️⃣ 【权限检查】问问策略顾问:这个窗口允许加吗?
int res = mPolicy.checkAddPermission(attrs, appOp); // 调用 PhoneWindowManager
if (res != ADD_OKAY) return res; // 没权限?直接拒绝!
synchronized (mWindowMap) { // 进入全局锁,防止多线程混乱
// 2️⃣ 【找屏幕】窗口要开在哪块屏幕上?(DisplayContent)
final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
if (displayContent == null) return ADD_INVALID_DISPLAY; // 屏幕不存在?拒绝!
// 3️⃣ 【子窗口?】如果是子窗口 (比如 Dialog 依附于 Activity)
if (type 是子窗口类型) {
// 必须找到它的父窗口!
parentWindow = windowForClientLocked(null, attrs.token, false);
if (parentWindow == null || parentWindow 也是子窗口) {
return ADD_BAD_SUBWINDOW_TOKEN; // 父窗口无效?拒绝!
}
}
// ... 第一阶段检查通过,进入下一关 ...
-
mPolicy.checkAddPermission(attrs, appOp)
:- 策略顾问 (
PhoneWindowManager
) 根据窗口类型 (type
)、申请者 UID/Package 等检查权限。 - 比如:普通 App 想添加一个系统级窗口 (
TYPE_SYSTEM_ALERT
),会被拒绝。 - 比喻:顾问翻看城市规划条例,看这种橱窗是否允许开在这个区域。
- 策略顾问 (
-
DisplayContent
:- 代表一块物理或虚拟屏幕。WMS 为每块屏幕管理其上的所有窗口。
- 通过
displayId
找到对应的DisplayContent
。找不到?说明屏幕无效,申请失败。 - 比喻:确认店铺想在哪个商圈(哪块屏幕)开橱窗。
-
子窗口检查:
- 子窗口 (如 Dialog、PopupWindow) 必须依附于一个父窗口 (通常是 Activity)。
- 通过传入的
attrs.token
(通常是父窗口的IBinder
令牌) 查找父窗口 (WindowState
)。 - 找不到父窗口或父窗口本身也是子窗口?申请失败。
- 比喻:开分店(子窗口)必须出示总店(父窗口)的授权证明。
🆔 阶段 2:令牌 (WindowToken
) 处理 (addWindow
Part 2)
kotlin
// ... 接上面 ...
// 4️⃣ 【找令牌】根据父窗口(如果有)或自身令牌,找对应的 WindowToken
WindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);
final int rootType = hasParent ? parentWindow.mAttrs.type : type; // 根窗口类型
// 5️⃣ 【令牌不存在?】
if (token == null) {
// 某些窗口类型【必须】有预注册的令牌 (如 Activity 的 TYPE_BASE_APPLICATION)
if (rootType 是应用窗口/输入法/壁纸等) {
return ADD_BAD_APP_TOKEN; // 没令牌?拒绝!
}
// 对于 Toast 等类型,可能允许【隐式创建】令牌
if (type == TYPE_TOAST) {
if (SDK 版本高且没令牌) return ADD_BAD_APP_TOKEN; // 高版本 Toast 也要令牌
}
// 6️⃣ 【创建隐式令牌】用 attrs.token 或 client 的 Binder 创建新 WindowToken
token = new WindowToken(this, binder, type, false, displayContent, ...);
} else {
// 7️⃣ 【令牌存在】检查令牌是否匹配窗口类型
if (rootType 是应用窗口) {
// 应用窗口的令牌必须是 AppWindowToken (Activity 专用)
atoken = token.asAppWindowToken();
if (atoken == null) return ADD_NOT_APP_TOKEN; // 不是 AppToken?拒绝!
if (atoken.removed) return ADD_APP_EXITING; // Activity 已退出?拒绝!
} else if (rootType == TYPE_INPUT_METHOD) {
if (token.windowType != TYPE_INPUT_METHOD) return ADD_BAD_APP_TOKEN; // 输入法令牌类型不对?拒绝!
}
// ... 其他类型检查 ...
}
// ... 令牌处理完毕,进入核心创建 ...
-
WindowToken
是关键! 它解决 "你是谁?" 和 "窗口分组" 的问题。 -
查找令牌:
- 尝试根据传入的
attrs.token
(或父窗口的 token) 找到对应的WindowToken
。
- 尝试根据传入的
-
令牌不存在时的处理:
- 强制要求令牌的窗口类型 :应用窗口 (
Activity
)、输入法 (IME
)、壁纸 (Wallpaper
) 等必须 有预注册的WindowToken
(通常由 AMS 在启动 Activity 时创建)。没有?直接拒绝申请! - 允许隐式创建的类型 :像
Toast
这样的简单窗口,如果符合条件(如低版本 SDK),WMS 会隐式创建 一个临时的WindowToken
(new WindowToken(...)
)。
- 强制要求令牌的窗口类型 :应用窗口 (
-
令牌存在时的检查:
- 应用窗口 (
Activity
) :对应的令牌必须 是AppWindowToken
(ActivityRecord
在 WMS 的映射)。不是?或者对应的 Activity 正在退出?拒绝! - 输入法窗口 :令牌类型必须匹配 (
TYPE_INPUT_METHOD
)。不匹配?拒绝! - 比喻:顾问检查授权书 (
WindowToken
) 的真伪和适用范围。开咖啡店不能用快餐店的授权书!
- 应用窗口 (
🛠️ 阶段 3:创建窗口档案 & 登记 (addWindow
Part 3)
scss
// ... 接上面 ...
// 8️⃣ 【创建 WindowState】这是窗口在 WMS 的核心代表!
final WindowState win = new WindowState(this, session, client, token, parentWindow, ...);
// 9️⃣ 【客户端检查】申请者还活着吗?(Binder 死亡?)
if (win.mDeathRecipient == null) return ADD_APP_EXITING; // 死了?拒绝!
// 🔟 【策略顾问微调】让顾问最后调整一下窗口参数 (LayoutParams)
mPolicy.adjustWindowParamsLw(win.mAttrs);
// 1️⃣1️⃣ 【策略顾问确认】顾问做最后的策略检查
res = mPolicy.prepareAddWindowLw(win, attrs);
if (res != ADD_OKAY) return res;
// 1️⃣2️⃣ 【关联客户端】建立 WindowState 和客户端 (App) 的链接
win.attach();
// 1️⃣3️⃣ 【登记!】将 WindowState 加入全局登记簿 (mWindowMap)
mWindowMap.put(client.asBinder(), win); // Key: client binder, Value: win
// 1️⃣4️⃣ 【加入令牌分组】将 WindowState 加入其 WindowToken 的管辖列表
win.mToken.addWindow(win);
// 1️⃣5️⃣ 【特殊类型处理】如果是输入法/壁纸等,触发额外逻辑
if (type == TYPE_INPUT_METHOD) {
setInputMethodWindowLocked(win); // 设置输入法窗口
} else if (type == TYPE_WALLPAPER) {
displayContent.mWallpaperController.scheduleLayout...(); // 壁纸需要重排
}
// ... 申请成功!后续可能触发布局和绘制 ...
return ADD_OKAY;
}
-
WindowState
诞生:- 这是最核心的一步 !
WindowState
对象包含了这个窗口在 WMS 内部管理所需的所有信息:Session、Token、父窗口、LayoutParams、可见性、与 App 端的 Binder 链接 (IWindow
) 等。 - 比喻:为这个新橱窗建立一份详细的档案 (
WindowState
)。
- 这是最核心的一步 !
-
客户端存活检查:
- 通过
mDeathRecipient
检查申请者(App 进程)是否还活着。死了?清理并拒绝。
- 通过
-
策略顾问最后把关:
adjustWindowParamsLw
:顾问根据全局策略微调窗口的LayoutParams
(比如调整某些标志位)。prepareAddWindowLw
:顾问做最终的策略检查(比如是否允许在该位置添加这种类型的窗口)。不通过?拒绝!
-
登记入册:
- 全局登记簿 (
mWindowMap.put()
) :将新创建的WindowState
以它的client
(IWindow
的 Binder) 为 Key 存入mWindowMap
。以后 WMS 操作这个窗口都靠这个 Key! - 加入令牌分组 (
win.mToken.addWindow(win)
) :将这个WindowState
添加到它所属的WindowToken
的窗口列表中。这样,关闭 Token(如 Activity 结束)时就能关掉所有关联窗口。 - 比喻:把新橱窗的档案 1) 录入城市总档案库 (
mWindowMap
), 2) 归入对应品牌的分店目录 (WindowToken
)。
- 全局登记簿 (
-
特殊类型处理:
- 如果是输入法窗口 (
TYPE_INPUT_METHOD
),需要更新 IME 目标窗口等状态。 - 如果是壁纸窗口 (
TYPE_WALLPAPER
) 或设置了FLAG_SHOW_WALLPAPER
,需要标记后续布局要重新处理壁纸。 - 比喻:特殊店铺(如消防站、银行)入驻,需要额外通知相关部门调整布局。
- 如果是输入法窗口 (
📌 总结:WMS 添加窗口的核心步骤
- 权限与基础检查 :策略顾问 (
mPolicy
) 检查权限,确认目标屏幕 (DisplayContent
) 有效,子窗口找到有效父窗口。 - 令牌 (
WindowToken
) 处理 :查找或创建令牌,严格检查令牌与窗口类型的匹配性(特别是应用窗口必须用AppWindowToken
)。 - 创建与登记 :创建窗口的核心档案
WindowState
,进行最后检查,将其登记到全局表 (mWindowMap
) 并加入令牌分组 (WindowToken
)。 - 特殊处理:对输入法、壁纸等特殊窗口类型进行额外配置。
核心数据结构关系图:
lua
DisplayContent (屏幕)
|
|-- WindowToken (品牌授权书 / Activity令牌)
|
|-- WindowState 1 (店铺1档案)
|-- WindowState 2 (店铺2档案)
|-- ...
|
|-- WindowToken (另一个令牌)
|
|-- ...
通过这个过程,WMS 将 App 申请的窗口(一个抽象概念)转化为了内部可管理的 WindowState
对象,并将其纳入到相应的屏幕 (DisplayContent
) 和令牌组 (WindowToken
) 中,为后续的布局、绘制、输入事件分发奠定了基础。理解 WindowToken
和 WindowState
是理解 WMS 如何管理成千上万窗口的关键!
通俗易懂的讲解:WMS核心团队与窗口添加流程
我将用"窗口管理局"的比喻,结合源码解析这篇文章的核心内容。文章主要讲两部分:WMS的核心成员 (管理局的部门架构)和窗口添加过程(新窗口的审批流程)。
🏢 第一章:窗口管理局的核心部门(WMS重要成员)
想象WMS是一个窗口管理局,它的核心成员就是各个职能部门:
📌 1. 策略规划部(mPolicy)
rust
java
Copy
final WindowManagerPolicy mPolicy; // 具体实现是PhoneWindowManager
- 职责:制定窗口管理的基本规则
- 比喻:城市规划局,决定哪些区域能开窗口、窗口的最大尺寸等
- 实际作用:处理按键事件(Home/Back键)、定义系统窗口行为
📋 2. 窗口登记处(mWindowMap)
java
java
Copy
final WindowHashMap mWindowMap = new WindowHashMap(); // Key: IBinder, Value: WindowState
-
职责:记录所有窗口的详细信息
-
比喻:房管局的不动产登记簿
-
数据结构:
IBinder
:窗口的唯一身份证(类似门牌号)WindowState
:窗口的完整档案(尺寸、位置、状态等)
🎫 3. 许可证管理处(mSessions)
ini
java
Copy
final ArraySet<Session> mSessions = new ArraySet<>();
- 职责:管理所有App的通信许可证
- 比喻:工商局颁发的营业执照
- 关键点:每个App进程对应一个Session,是App与WMS通信的通道
🏷️ 4. 窗口令牌中心(WindowToken)
rust
java
Copy
final ArrayList<AppWindowToken> mFinishedStarting;
-
职责:管理窗口的身份令牌
-
核心概念:
WindowToken
:窗口组的通行证(比如一个Activity的所有窗口)AppWindowToken
:Activity专属令牌(继承自WindowToken)
-
比喻:连锁店的总授权书,旗下分店共用同一授权
🎬 5. 特效动画组(mAnimator)
rust
java
Copy
final WindowAnimator mAnimator;
- 职责:管理窗口动画
- 实际工作:处理窗口打开/关闭的过渡动画
🖱️ 6. 事件调度中心(mInputManager)
rust
java
Copy
final InputManagerService mInputManager;
- 职责:将触摸事件分发给正确窗口
- 协作机制:WMS提供窗口位置信息,IMS负责事件分发
📝 第二章:新窗口开业申请流程(addWindow过程)
当App要添加新窗口(如Activity/Dialog),需向WMS提交申请,流程如下:
🔍 阶段1:资质预审(Part 1)
ini
java
Copy
public int addWindow(Session session, IWindow client, ...) {
// 1. 权限检查
int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != ADD_OKAY) return res; // 无权限直接拒绝
// 2. 确定显示位置
DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
if (displayContent == null) return ADD_INVALID_DISPLAY;
// 3. 子窗口特殊检查
if (isSubWindow) {
WindowState parentWindow = windowForClientLocked(null, attrs.token, false);
if (parentWindow == null) return ADD_BAD_SUBWINDOW_TOKEN;
}
}
- 权限检查:策略部(PhoneWindowManager)审核窗口类型是否合法
- 场地验证:确认要显示的屏幕(DisplayContent)是否存在
- 子窗口要求:类似分店申请,必须提供总店(父窗口)证明
🆔 阶段2:令牌核验(Part 2)
ini
java
Copy
WindowToken token = displayContent.getWindowToken(attrs.token);
if (token == null) {
// 系统窗口自动发牌
if (isSystemWindowType) {
token = new WindowToken(this, binder, type, false, displayContent);
}
// 普通窗口必须持牌
else if (isApplicationWindow) {
return ADD_BAD_APP_TOKEN; // 无令牌拒绝
}
}
// 应用窗口需特殊令牌
else if (isApplicationWindow) {
AppWindowToken atoken = token.asAppWindowToken();
if (atoken == null) return ADD_NOT_APP_TOKEN;
}
-
令牌检查:
- 系统窗口(如Toast):WMS自动颁发临时令牌
- 应用窗口(如Activity):必须提供AppWindowToken
-
关键规则:

🏗️ 阶段3:建档入驻(Part 3)
scss
java
Copy
// 1. 创建窗口档案
final WindowState win = new WindowState(this, session, client, token, ...);
// 2. 客户端存活检查
if (win.mDeathRecipient == null) return ADD_APP_EXITING;
// 3. 策略部最后调整
mPolicy.adjustWindowParamsLw(win.mAttrs);
mPolicy.prepareAddWindowLw(win, attrs);
// 4. 正式登记注册
win.attach();
mWindowMap.put(client.asBinder(), win); // 加入全局登记簿
token.addWindow(win); // 加入令牌组
// 5. 特殊窗口处理
if (type == TYPE_INPUT_METHOD) {
setInputMethodWindowLocked(win); // 设为输入法窗口
} else if (type == TYPE_WALLPAPER) {
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
-
核心步骤:
-
创建WindowState:建立窗口完整档案
-
死亡检查:确认申请进程存活
-
参数微调:策略部最后调整窗口参数
-
双重登记:
- 全局登记(mWindowMap)
- 令牌分组(WindowToken)
-
特殊处理:输入法/壁纸等特殊窗口额外配置
-
💡 关键概念解析
-
WindowState:
-
窗口的"身份证+档案"
-
包含大小、位置、层级等所有信息
-
示例代码:
arduinojava Copy class WindowState { final Session mSession; // 所属App final WindowToken mToken; // 所属令牌组 final IWindow mClient; // 客户端连接 int mRequestedWidth; // 请求宽度 int mLayer; // 在Z轴的层级 }
-
-
WindowToken:
-
窗口组的逻辑容器
-
关键作用:
-

-
DisplayContent:
-
代表物理/虚拟屏幕
-
管理屏幕上的所有窗口
-
层级结构:
scssCopy DisplayContent ├── WindowToken (Activity1) │ ├── WindowState (主窗口) │ └── WindowState (Dialog) └── WindowToken (Activity2) └── WindowState (主窗口)
-
📌 总结:窗口添加全景图

通过这个流程,WMS完成了:
- 权限审核 → 2. 资源分配 → 3. 档案创建 → 4. 全局登记 → 5. 特殊处理
最终新窗口被纳入WMS的统一管理体系,为后续的布局、绘制、事件处理奠定基础。这就是Android窗口管理的核心机制!