WMS/AMS深入WindowState如何正确找到自己在层级结构树中位置进行挂载

背景

在正常系统刚启动,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]

构建步骤:

  1. 遍历所有 Feature (如 FEATURE_IMEFEATURE_NAVIGATION_BAR 等),每个 Feature 声明了它覆盖的 layer 范围
  2. 按 layer 逐层扫描 :如果当前 layer 的 Feature 集合与上一层不同,就创建新的 DisplayArea 节点
  3. 叶子节点创建 :每个连续的、同类型的 layer 范围共用一个 DisplayArea.Tokens
  4. 填充 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_ALERTTYPE_SYSTEM_OVERLAYTYPE_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 排序

原文地址

https://mp.weixin.qq.com/s/SBwAXj449z3Zt0Z1Hh2AGQ

相关推荐
LinDaiDai_霖呆呆1 小时前
做 Agent 开发入门必懂的 10 个 Agent 核心概念
前端·agent·ai编程
6666v61 小时前
深入 Android 统一状态模型:MVI 架构的核心实现
android·kotlin
Digitally1 小时前
能否通过蓝牙从安卓传输文件到 iPhone?6 种替代方法
android·iphone
CocoaKier1 小时前
发现豆包有一个趣又实用的功能,文章链接转播客
ai编程
硬件学长森哥1 小时前
Android影像基础-3A在系统平台中的实现
android·图像处理·计算机视觉
私人珍藏库1 小时前
[Android] 哔哩哔哩第三方安卓电视TVapp BV_0.3.16.r898
android·app·工具·软件·多功能
LuDvei1 小时前
android Build Tools安装API选择AVD模拟器下载及设置等操作
android
亘元有量-流量变现1 小时前
小米应用商店ASO优化:紧抓3大核心位,高效提升关键词覆盖
android·aso优化·亘元有量·方糖试玩
YF02112 小时前
Google ML 技术如何在 Android 上落地
android·ai编程·core ml