13、WindowState 详解:WindowState 的创建、属性存储、与 IWindow 的关联、窗口状态的维护

WindowState 这个类,说白了就是 WMS 里每个窗口的「身份证」加「档案袋」。我刚开始啃 WMS 源码时,总觉得它就是个存属性的容器,后来踩了几个坑才明白——它其实是整个窗口管理系统的核心枢纽。

今天我们就把它彻底拆开看看。

13.1 WindowState 的创建时机

什么时候会创建 WindowState?答案很简单:当应用调用 WindowManager.addView() 时,一路走到 WMS 的 addWindow() 方法,就会 new 一个 WindowState 出来。

我贴一段核心流程的伪代码,你感受一下:

// WMS.addWindow() 中的关键片段
public int addWindow(Session session, IWindow client, ...) {
    // ... 各种权限校验、类型检查 ...

    WindowState win = new WindowState(this, session, client, token,
            parentWindow, appOp, seq, attrs, viewVisibility, ownerId,
            packageName, requestWidth, requestHeight, type, flags, colorMode);

    // 加入窗口列表
    mWindowMap.put(client.asBinder(), win);
    mWindows.add(win);

    // ... 后续的层级分配、焦点计算 ...
}

嗯,这里要注意:每个 IWindow 对应唯一一个 WindowState。IWindow 是 Binder 对象,WindowState 通过它跟应用进程通信。我见过有人误以为一个 SurfaceView 会创建多个 WindowState,其实不会——SurfaceView 的窗口是独立的,但它的 WindowState 只有一个。

13.2 属性存储:WindowState 里到底存了什么?

WindowState 的属性,我习惯分成四类来看:

类别 关键字段 说明
身份信息 mSession, mOwnerId, mPackageName, mAttrs.token 谁创建的、属于哪个应用
布局属性 mRequestedWidth/Height, mFrame, mContentFrame 窗口想要多大、实际多大
状态标志 mViewVisibility, mPolicyVisibility, mHasSurface 可见性、Surface 是否已创建
层级关系 mParentWindow, mChildWindows, mBaseLayer, mSubLayer 父子窗口、Z-order 层级

我个人习惯把 mAttrs 单独拎出来看。它是 WindowManager.LayoutParams 对象,里面存了 flags、type、gravity、softInputMode 等一大堆东西。说白了,应用层传进来的所有窗口配置,最后都落在这个对象里。

核心要点:WindowState 的属性不是一成不变的。比如窗口旋转时,mFrame 会被重新计算;用户切换应用时,mViewVisibility 会变化。这些属性的变更,最终都会触发 relayout 流程。

13.3 与 IWindow 的关联:Binder 通道的秘密

IWindow 是什么?它是应用进程暴露给 WMS 的一个 Binder 接口。WMS 通过它来通知应用:

  • 窗口位置变了 → mClient.resized()
  • 窗口获得焦点 → mClient.windowFocusChanged()
  • 需要更新配置 → mClient.dispatchAppVisibility()

你想想看,如果 IWindow 这个 Binder 对象挂了(比如应用进程被杀),WMS 怎么知道?

答案就在 WindowState.mDeathRecipient 里。WindowState 创建时,会注册一个死亡回调:

// WindowState 构造函数中
mDeathRecipient = new DeathRecipient() {
    @Override
    public void binderDied() {
        // 应用进程挂了,WMS 需要清理这个窗口
        postWindowRemoveMessage(mClient.asBinder());
    }
};
mClient.asBinder().linkToDeath(mDeathRecipient, 0);

我曾经在一个项目里遇到过诡异的内存泄漏,排查了半天,最后发现是 unlinkToDeath 没调,导致死亡回调一直持有 WindowState 引用。嗯,这个坑你们要注意。

13.4 窗口状态的维护:从生到死的生命周期

窗口状态不是非黑即白的。我把它画成了一张状态流转图:

INIT(创建) ADDED(已添加) VISIBLE(可见) GONE(不可见) REMOVED(移除) addWindow() relayout() hide() removeWindow() removeWindow() show() / hide()

这张图里,我特别想强调两点:

  1. VISIBLE 和 GONE 之间可以来回切换。比如按 Home 键时,当前应用的窗口会从 VISIBLE 切到 GONE,但 WindowState 还在,只是不可见。
  2. REMOVED 是终态。一旦进入 REMOVED,WindowState 就会被从 mWindowMap 中移除,并且 unlinkToDeath。之后这个对象就等着 GC 回收了。

避坑指南:我曾经遇到过一个 bug,应用退到后台后窗口状态变成了 GONE,但 Surface 没有及时释放,导致内存暴涨。后来发现是 mHasSurface 标志位没在 GONE 状态时清掉。所以,状态维护一定要跟 Surface 生命周期联动起来。

13.5 窗口状态的「隐形维度」

除了上面说的显式状态,WindowState 还有几个「隐形维度」需要关注:

  • 焦点状态mWinAnimator.mDrawState 控制着窗口的绘制阶段,比如 COMMIT、READY、HAS_DRAWN 等。这跟用户看到的「窗口是否显示完整」直接相关。
  • 动画状态:窗口在进入/退出时,会经历动画过程。WindowState 的 mAnimatingExit 标志位就是用来标记「正在播放退出动画」的。
  • 配置状态mConfigurationSeq 记录了窗口当前的配置序号。如果跟全局配置不一致,就需要触发 relayout。

说白了,一个 WindowState 的状态不是单一维度的。它同时处于「可见/不可见」「有焦点/无焦点」「动画中/静止」「已绘制/未绘制」等多个状态空间中。WMS 的很多 bug,本质上就是这些状态组合没处理好。

注意事项:不要试图在 WMS 主线程里直接修改 WindowState 的 mFrame 或 mContentFrame。这些字段的修改必须经过 performLayoutAndPlaceSurfacesLocked() 的流程,否则会导致布局不一致。我见过有人为了「快速修复」直接 setFrame(),结果引发了 ANR。

好了,WindowState 的核心内容就这些。它不复杂,但细节很多。你只要记住:它是窗口的身份证、档案袋、状态机,WMS 里所有跟窗口相关的逻辑,最终都会落到 WindowState 上。


公众号:蓝海资料掘金营,微信deep3321