WMS 的核心成员和窗口添加过程

📚 ​​故事背景:窗口管理局的日常​

在上一集(WMS的诞生)中,我们见证了 ​​窗口管理局(WMS)​​ 的成立。现在,管理局开始正式运作了!它需要:

  1. ​组建核心团队​:招聘各部门主管(WMS 的重要成员变量)。
  2. ​处理开新店申请​ :当 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),以后办事都找他。
  • mWindowMap (全局窗口登记簿 - WindowHashMap)​​:

    • 这是一个​核心数据库​ !键 (Key) 是窗口的唯一标识 IBinder(想象成窗口的身份证号),值 (Value) 是 WindowState(窗口的详细档案)。
    • WindowState 是 WMS 内部描述一个窗口的​核心数据结构​,包含窗口大小、位置、层级、Surface 等所有信息。
    • 比喻:一本厚厚的登记簿,记录着城市里​每一个橱窗​ (窗口)的详细信息(WindowState)。
  • mFinishedStarting / mFinishedEarlyAnim / ... (各种名单 - ArrayList<AppWindowToken>)​​:

    • 这些列表管理着代表应用程序窗口(主要是 Activity)的 AppWindowToken

    • AppWindowTokenWindowToken 的子类,代表一个 ​​Activity 的窗口令牌​​。

    • WindowToken 的核心作用​​:

      1. ​身份令牌​ :App 想开新窗口(比如 Activity、Dialog),必须出示对应的 WindowToken(证明你是合法店铺)。系统窗口(如状态栏)由 WMS 内部管理。
      2. ​分组管理​ :把属于同一个组件(如一个 Activity)的多个窗口(WindowState)归到一起,方便统一操作(比如关闭 Activity 时关掉所有它的窗口)。
    • 比喻:

      • WindowToken 相当于一个 ​品牌授权书​(比如"星巴克授权书")。有了它,才能开星巴克窗口(店铺)。
      • AppWindowToken 是给 ​Activity 用的特殊授权书​(旗舰店授权书)。
      • mFinishedStarting 记录那些 ​已经完成开业手续​ 的旗舰店 (AppWindowToken)。
  • mAnimator (动画特效总监 - WindowAnimator)​​:

    • 负责管理和执行所有窗口的动画(打开、关闭、切换时的效果)。
    • 比喻:橱窗特效师,负责让店铺切换展示时更炫酷。
  • mInputManager (事件处理局长 - InputManagerService)​​:

    • 负责处理用户的触摸、按键事件。WMS 持有它的引用,因为事件最终要分发给正确的窗口(WindowState),而 WMS 知道所有窗口的位置和层级。
    • 比喻:市民(用户)在橱窗上点击,事件局 (IMS) 需要问窗口管理局 (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)​ :对应的令牌​必须​AppWindowTokenActivityRecord 在 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 添加窗口的核心步骤​

  1. ​权限与基础检查​ :策略顾问 (mPolicy) 检查权限,确认目标屏幕 (DisplayContent) 有效,子窗口找到有效父窗口。
  2. ​令牌 (WindowToken) 处理​ :查找或创建令牌,严格检查令牌与窗口类型的匹配性(特别是应用窗口必须用 AppWindowToken)。
  3. ​创建与登记​ :创建窗口的核心档案 WindowState,进行最后检查,将其登记到全局表 (mWindowMap) 并加入令牌分组 (WindowToken)。
  4. ​特殊处理​:对输入法、壁纸等特殊窗口类型进行额外配置。

​核心数据结构关系图​​:

lua 复制代码
DisplayContent (屏幕)
  |
  |-- WindowToken (品牌授权书 / Activity令牌)
        |
        |-- WindowState 1 (店铺1档案)
        |-- WindowState 2 (店铺2档案)
        |-- ...
  |
  |-- WindowToken (另一个令牌)
        |
        |-- ...

通过这个过程,WMS 将 App 申请的窗口(一个抽象概念)转化为了内部可管理的 WindowState 对象,并将其纳入到相应的屏幕 (DisplayContent) 和令牌组 (WindowToken) 中,为后续的布局、绘制、输入事件分发奠定了基础。理解 WindowTokenWindowState 是理解 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;
}
  • ​核心步骤​​:

    1. ​创建WindowState​​:建立窗口完整档案

    2. ​死亡检查​​:确认申请进程存活

    3. ​参数微调​​:策略部最后调整窗口参数

    4. ​双重登记​​:

      • 全局登记(mWindowMap)
      • 令牌分组(WindowToken)
    5. ​特殊处理​​:输入法/壁纸等特殊窗口额外配置


💡 关键概念解析

  1. ​WindowState​​:

    • 窗口的"身份证+档案"

    • 包含大小、位置、层级等所有信息

    • 示例代码:

      arduino 复制代码
      java
      Copy
      class WindowState {
          final Session mSession;  // 所属App
          final WindowToken mToken; // 所属令牌组
          final IWindow mClient;   // 客户端连接
          int mRequestedWidth;     // 请求宽度
          int mLayer;              // 在Z轴的层级
      }
  2. ​WindowToken​​:

    • 窗口组的逻辑容器

    • 关键作用:

  1. ​DisplayContent​​:

    • 代表物理/虚拟屏幕

    • 管理屏幕上的所有窗口

    • 层级结构:

      scss 复制代码
      Copy
      DisplayContent
      ├── WindowToken (Activity1)
      │   ├── WindowState (主窗口)
      │   └── WindowState (Dialog)
      └── WindowToken (Activity2)
          └── WindowState (主窗口)

📌 总结:窗口添加全景图

通过这个流程,WMS完成了:

  1. 权限审核 → 2. 资源分配 → 3. 档案创建 → 4. 全局登记 → 5. 特殊处理

最终新窗口被纳入WMS的统一管理体系,为后续的布局、绘制、事件处理奠定基础。这就是Android窗口管理的核心机制!

相关推荐
用户2018792831676 小时前
通俗易懂的讲解:Android系统启动全流程与Launcher诞生记
android
二流小码农6 小时前
鸿蒙开发:资讯项目实战之项目框架设计
android·ios·harmonyos
用户2018792831678 小时前
PMS 创建之“软件包管理超级工厂”的建设
android
用户2018792831678 小时前
通俗易懂的讲解:Android APK 解析的故事
android
渣渣_Maxz8 小时前
使用 antlr 打造 Android 动态逻辑判断能力
android·设计模式
Android研究员8 小时前
HarmonyOS实战:List拖拽位置交换的多种实现方式
android·ios·harmonyos
guiyanakaung8 小时前
一篇文章让你学会 Compose Multiplatform 推荐的桌面应用打包工具 Conveyor
android·windows·macos
恋猫de小郭9 小时前
Flutter 应该如何实现 iOS 26 的 Liquid Glass ,它为什么很难?
android·前端·flutter
葱段9 小时前
【Compose】Android Compose 监听TextField粘贴事件
android·kotlin·jetbrains