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窗口管理的核心机制!

相关推荐
zzhongcy37 分钟前
复合索引 (item1, item2, item3 ) > (?, ?, ?) 不起作用,EXPLAIN 后type=ALL(全表扫描)
android·数据库
冬奇Lab1 小时前
稳定性性能系列之十三——CPU与I/O性能优化:Simpleperf与存储优化实战
android·性能优化
像风一样自由2 小时前
android native 中的函数动态注册方式总结
android·java·服务器·安卓逆向分析·native函数动态注册·.so文件分析
nono牛2 小时前
Makefile中打印变量
android
没有了遇见3 小时前
Android 关于RecycleView和ViewPager2去除边缘反馈
android
城东米粉儿3 小时前
android gzip数据压缩 笔记
android
城东米粉儿3 小时前
android 流量优化笔记
android
似霰4 小时前
HIDL Hal 开发笔记10----添加硬件访问服务(Java 层调用 HIDL)
android·framework·hal
佛系打工仔6 小时前
绘制K线第三章:拖拽功能实现
android·前端·ios
我命由我123456 小时前
Android 项目路径包含非 ASCII 字符问题:Your project path contains non-ASCII characters
android·java·java-ee·android studio·android jetpack·android-studio·android runtime