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 窗口状态的维护:从生到死的生命周期
窗口状态不是非黑即白的。我把它画成了一张状态流转图:
这张图里,我特别想强调两点:
- VISIBLE 和 GONE 之间可以来回切换。比如按 Home 键时,当前应用的窗口会从 VISIBLE 切到 GONE,但 WindowState 还在,只是不可见。
- 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