背景
在正常系统刚启动,wms层面就给我们创建如下的层级结构树,这个时候可以看到是没有任何具体的显示窗口画面,即没有具体WindowState等。
因为具体的窗口等都是由各个App自行进行触发创建,比如StatusBar,NavigationBar等窗口都是SystemUI创建,Home 桌面Activity由Launcher创建,变成如下有窗口的画面的层级结构树。
那么大家是否有想过,一个窗口是如果从app层面addView了之后就可以挂载到最开始的层级结构树呢?
这里大家可能会看到,普通窗口明显是多了一个WindowToken节点和WindowState节点。
今天带大家剖析的就是这个WindowToken是如何正确的找到自己才层级结构树的位置。
一、正常窗口的层级结构树概览
在 Android WMS 中,窗口层级结构是一个树,节点的从属关系如下:
DisplayContent
└── RootDisplayArea (可能有多个,如多屏/分屏)
└── DisplayArea (中间节点,按 Feature 分组,如 IME、StatusBar)
└── DisplayArea.Tokens (叶子容器,存 WindowToken)
└── WindowToken (持有相同 binder token 的一组窗口)
└── WindowState (实际的窗口)
每个节点都是 WindowContainer 的子类,具有父子关系 (mChildren / mParent),每个节点对应一个 SurfaceControl。层级的 z-order 由 assignChildLayers() 在整棵树上递归决定。
二、app --> system 完整的 addWindow 调用链源码剖析
这里以 TYPE_APPLICATION_OVERLAY 为例
app端触发addWindow
客户端(SystemUI 或三方 App)调用:
java
// WindowManagerImpl.java
mGlobal.addView(view, params, display, parentWindow, userId);
最终进入 WindowManagerService.addWindow()。
WMS端:WindowToken 的查找与创建
文件 : services/core/java/com/android/server/wm/WindowManagerService.java:1541-1578
java
// 1) 先从 DisplayContent 的 mTokenMap 中查找是否已有对应 IBinder 的 token
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
if (token == null) {
// 2) 找不到则创建新的 WindowToken
if (hasParent) {
// 子窗口复用父窗口的 token
token = parentWindow.mToken;
} else if (mWindowContextListenerController.hasListener(windowContextToken)) {
// WindowContext 路径 (WindowProviderService),携带 Bundle options
token = new WindowToken.Builder(this, binder, type)
.setDisplayContent(displayContent)
.setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
.setRoundedCornerOverlay(isRoundedCornerOverlay)
.setFromClientToken(true)
.setOptions(options)
.build();
} else {
// ★ 标准系统 overlay 窗口走这里
final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
token = new WindowToken.Builder(this, binder, type)
.setDisplayContent(displayContent)
.setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
.setRoundedCornerOverlay(isRoundedCornerOverlay)
.build();
}
}
关键参数:
| 参数 | 含义 |
|---|---|
binder |
Token 的标识符,一般是客户端的 IWindowSession binder 或 attrs.token |
type |
窗口类型,如 TYPE_APPLICATION_OVERLAY |
displayContent |
目标 DisplayContent |
mOwnerCanManageAppTokens |
caller 是否有 MANAGE_APP_TOKENS 权限(系统级 caller 为 true) |
WindowToken 构造 --- 触发 DisplayContent.addWindowToken()
文件 : services/core/java/com/android/server/wm/WindowToken.java:202-216
java
protected WindowToken(WindowManagerService service, IBinder _token, int type,
boolean persistOnEmpty, DisplayContent dc, boolean ownerCanManageAppTokens,
boolean roundedCornerOverlay, boolean fromClientToken, @Nullable Bundle options) {
super(service);
token = _token;
windowType = type;
mOptions = options;
// ... 其他字段赋值 ...
if (dc != null) {
// ★★★ 关键调用:将自身注册到 DisplayContent 的层级树中
dc.addWindowToken(token, this);
}
}
Builder 只是传参,最终 build() 调用了上面的构造函数。
真正核心部分:是层级定位 --- findAreaForToken()
文件 : services/core/java/com/android/server/wm/DisplayContent.java:1753-1785
java
void addWindowToken(IBinder binder, WindowToken token) {
// 防重复:同一个 binder 不能映射到两个显示设备
final DisplayContent dc = mWmService.mRoot.getWindowTokenDisplay(token);
if (dc != null) {
throw new IllegalArgumentException(...);
}
// 存入 DisplayContent 级别的 token 映射表
mTokenMap.put(binder, token);
if (token.asActivityRecord() == null) {
// 非 Activity 窗口:直接加到 DisplayArea 树
token.mDisplayContent = this;
final DisplayArea.Tokens da = findAreaForToken(token).asTokens();
da.addChild(token); // ★ 将 token 作为子节点插入 Tokens 容器
}
// Activity 窗口走 Task → TaskDisplayArea 的路径,此处不走
}
findAreaForToken() 的调用链:
java
// DisplayContent.java:7159
DisplayArea findAreaForToken(WindowToken windowToken) {
return findAreaForWindowType(windowToken.getWindowType(), windowToken.mOptions,
windowToken.mOwnerCanManageAppTokens, windowToken.mRoundedCornerOverlay);
}
// DisplayContent.java:7131
DisplayArea findAreaForWindowType(int windowType, ...) {
// 1) Application windows → TaskDisplayArea
if (windowType >= FIRST_APPLICATION_WINDOW && windowType <= LAST_APPLICATION_WINDOW) {
return mDisplayAreaPolicy.getTaskDisplayArea(options);
}
// 2) IME windows → ImeContainer
if (windowType == TYPE_INPUT_METHOD || windowType == TYPE_INPUT_METHOD_DIALOG) {
return getImeContainer();
}
// 3) ★ Overlay 等系统窗口 → DisplayAreaPolicy 决策
return mDisplayAreaPolicy.findAreaForWindowType(windowType, options,
ownerCanManageAppToken, roundedCornerOverlay);
}
层级数字Layer → DisplayArea.Tokens 映射(层级树的核心)
文件 : services/core/java/com/android/server/wm/RootDisplayArea.java:114-129
java
DisplayArea.Tokens findAreaForTokenInLayer(WindowToken token) {
return findAreaForWindowTypeInLayer(token.windowType,
token.mOwnerCanManageAppTokens, token.mRoundedCornerOverlay);
}
DisplayArea.Tokens findAreaForWindowTypeInLayer(int windowType, ...) {
// ★ 关键:通过 Policy 获取窗口类型对应的 layer 编号
int windowLayerFromType = mWmService.mPolicy.getWindowLayerFromTypeLw(
windowType, ownerCanManageAppTokens, roundedCornerOverlay);
if (windowLayerFromType == APPLICATION_LAYER) {
throw new IllegalArgumentException(
"There shouldn't be WindowToken on APPLICATION_LAYER");
}
// ★ 用 layer 编号直接索引数组,O(1) 找到对应的 Tokens 叶子
return mAreaForLayer[windowLayerFromType];
}
这个 mAreaForLayer 数组是关键。它是一个按 layer 编号索引的数组(大小 37,对应 policy 的最大层级 36),每个槽位指向该 layer 所属的 DisplayArea.Tokens 叶子节点。
三、DisplayArea 层级树的构建(mAreaForLayer 的由来)
文件 : services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java:518-588
在 DisplayAreaPolicy 初始化时,会根据定义的 Feature 列表和每个 Feature 生效的 layer 范围,构建一棵树:
Root
│
┌───────────────┼───────────────┐
│ │ │
FEATURE_A FEATURE_B(Layer5-8) FEATURE_C(Layer10-15)
│ │ │
Tokens[0-4] Tokens[5-8] Tokens[10-15]
构建步骤:
- 遍历所有 Feature (如
FEATURE_IME、FEATURE_NAVIGATION_BAR等),每个 Feature 声明了它覆盖的 layer 范围 - 按 layer 逐层扫描 :如果当前 layer 的 Feature 集合与上一层不同,就创建新的
DisplayArea节点 - 叶子节点创建 :每个连续的、同类型的 layer 范围共用一个
DisplayArea.Tokens - 填充
mAreaForLayer数组 :将每个 layer 映射到其对应的DisplayArea.Tokens
java
// DisplayAreaPolicyBuilder.java:962-966
private void fillAreaForLayers(DisplayArea.Tokens leaf, DisplayArea.Tokens[] areaForLayer) {
for (int i = mMinLayer; i <= mMaxLayer; i++) {
areaForLayer[i] = leaf; // 范围内的所有 layer 共享同一个 Tokens 叶子
}
}
四、Policy Layer 编号规则
文件 : services/core/java/com/android/server/policy/WindowManagerPolicy.java:500-619
getWindowLayerFromTypeLw() 是主层的映射函数,决定了每种窗口类型的基础层号:
| 窗口类型 | 系统 caller layer | 非系统 caller layer |
|---|---|---|
| TYPE_WALLPAPER | 1 | 1 |
| TYPE_PHONE / TYPE_DOCK_DIVIDER / TYPE_QS_DIALOG | 3 | 3 |
| TYPE_TOAST | 7 | 7 |
| TYPE_SYSTEM_ALERT | 12 | 9 |
| TYPE_APPLICATION_OVERLAY | 11 | 11 |
| TYPE_STATUS_BAR | 15 | 15 |
| TYPE_SYSTEM_OVERLAY | 23 | 10 |
| TYPE_NAVIGATION_BAR | 24 | 24 |
| TYPE_SYSTEM_ERROR | 27 | 9 |
| TYPE_ACCESSIBILITY_OVERLAY | 31 | 31 |
| TYPE_SECURE_SYSTEM_OVERLAY | 33 | 33 |
| TYPE_POINTER | 35 | 35 |
| Rounded Corner Overlay | 36 (max) | --- |
注意:
- 有些类型(如
TYPE_SYSTEM_ALERT、TYPE_SYSTEM_OVERLAY、TYPE_SYSTEM_ERROR)对系统 caller 和非系统 caller 返回不同的 layer。这就是为什么 SystemUI 的 overlay 在更高的 z 层,而普通 app 的 overlay 在较低层 TYPE_APPLICATION_OVERLAY固定返回 11,不论 caller 身份
五、完整流程图总结
以下是一个 TYPE_APPLICATION_OVERLAY 窗口的完整加入流程:
Client: windowManager.addView(overlayView, params)
│
▼
WindowManagerService.addWindow()
│
├─[1] displayContent.getWindowToken(attrs.token)
│ └─ mTokenMap.get(binder) → null (首次添加)
│
├─[2] new WindowToken.Builder(this, binder, TYPE_APPLICATION_OVERLAY)
│ .setDisplayContent(displayContent)
│ .setOwnerCanManageAppTokens(true/false)
│ .build()
│ │
│ └─[3] WindowToken 构造函数
│ └─ dc.addWindowToken(token, this)
│ │
│ ├─ mTokenMap.put(binder, token) // 注册到 DisplayContent
│ │
│ ├─[4] findAreaForToken(token) // 定位 DisplayArea.Tokens
│ │ └─ findAreaForWindowType(TYPE_APPLICATION_OVERLAY, ...)
│ │ └─ mDisplayAreaPolicy.findAreaForWindowType(...)
│ │ └─ RootDisplayArea.findAreaForTokenInLayer(token)
│ │ └─ getWindowLayerFromTypeLw() → 11
│ │ └─ mAreaForLayer[11] → DisplayArea.Tokens 实例
│ │
│ └─[5] da.addChild(token) // 插入 Tokens 叶子容器
│ └─ addChild(token, mWindowComparator)
│ └─ 按 getWindowLayerFromType()=11 排序插入
│
├─[6] new WindowState(this, session, client, token, ...)
│ └─ mBaseLayer = 11 * 10000 + 1000 = 111000
│
├─[7] win.mToken.addWindow(win) // WindowState 加入 WindowToken
│ └─ 按 mBaseLayer 排序
原文地址