我将用"窗口管理局"的比喻,结合源码解析这篇文章的核心内容。整个过程就像关闭一家店铺(删除窗口),需要经过本地清理和中央注销两大阶段:
🏢 第一章:本地清理准备(WindowManagerGlobal阶段)
想象你是一家店铺(窗口)的老板,现在要关店停业:
1. 提交关店申请(removeView())
java
Copy
// 代码路径:frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void removeView(View view, boolean immediate) {
int index = findViewLocked(view, true); // 1. 查找店铺登记号
removeViewLocked(index, immediate); // 2. 启动关闭流程
}
- 比喻:你到工商局(
WindowManagerGlobal)说:"我要关闭3号店铺(index=3)" - 参数:
immediate决定是否立即关店(紧急停业 vs 正常打烊)
2. 停业前准备(removeViewLocked())
java
Copy
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index); // 1. 找到店长(ViewRootImpl)
// 2. 关闭收银系统(输入法)
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
// 3. 通知店长处理关店
boolean deferred = root.die(immediate);
}
- 关键对象:
ViewRootImpl是窗口的实际管理者(类似店长) - 输入法处理:类似"撤下店铺的扫码枪",防止关店后键盘残留
3. 店长执行关店(ViewRootImpl.die())
java
Copy
// 代码路径:frameworks/base/core/java/android/view/ViewRootImpl.java
boolean die(boolean immediate) {
// 情况1:立即关店且当前没有顾客(非布局阶段)
if (immediate && !mIsInTraversal) {
doDie(); // 直接关店
return false; // 无需延迟
}
// 情况2:延迟关店(挂打烊牌子)
mHandler.sendEmptyMessage(MSG_DIE);
return true; // 标记为延迟处理
}
- 状态检查:
mIsInTraversal表示是否正在布局/绘制(类似"店铺是否在接待顾客") - 延迟机制:如果店铺正忙,先挂"打烊中"牌子(发消息),等闲时再关
4. 正式关店操作(doDie())
java
Copy
void doDie() {
checkThread(); // 1. 必须由开店时的同一人操作(线程安全检查)
synchronized (this) {
if (mRemoved) return; // 2. 防止重复关店
mRemoved = true; // 标记为"已关闭"
if (mAdded) {
dispatchDetachedFromWindow(); // 3. 解绑窗口资源
}
// 4. 拆除店铺招牌(释放渲染资源)
if (mAdded && !mFirst) {
destroyHardwareRenderer();
mSurface.release();
}
// 5. 从工商局名单移除
WindowManagerGlobal.getInstance().doRemoveView(this);
}
}
- 线程检查:
checkThread()确保操作在UI线程(开店关店必须同一人) - 核心操作:
dispatchDetachedFromWindow()解绑窗口(下一章详解)
5. 从本地名单移除(doRemoveView())
java
Copy
void doRemoveView(ViewRootImpl root) {
synchronized (mLock) {
int index = mRoots.indexOf(root); // 1. 找店长编号
if (index >= 0) {
// 2. 从三大名单清除
mRoots.remove(index); // 店长名单
mParams.remove(index); // 店铺参数档案
mViews.remove(index); // 店铺视图档案
}
}
}
- 清理档案:窗口管理局本地不再记录该店铺
🏢 第二章:中央注销登记(WMS阶段)
1. 申请窗口注销(dispatchDetachedFromWindow())
java
Copy
void dispatchDetachedFromWindow() {
try {
// 通过Binder呼叫中央管理局
mWindowSession.remove(mWindow); // mWindow是窗口的唯一ID
} catch (RemoteException e) { ... }
}
- 通信桥梁:
mWindowSession是App与WMS的通信通道(类似工商局专线)
2. 管理局接待处理(Session.remove())
java
Copy
// 代码路径:frameworks/base/services/core/java/com/android/server/wm/Session.java
public void remove(IWindow window) {
mService.removeWindow(this, window); // 转交WMS处理
}
- 中转站:Session将请求转给真正的管理局(WMS)
3. 查找窗口档案(WMS.removeWindow())
java
Copy
// 代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
void removeWindow(Session session, IWindow client) {
synchronized (mWindowMap) {
// 1. 根据窗口ID查找档案(WindowState)
WindowState win = windowForClientLocked(session, client, false);
if (win == null) return;
// 2. 尝试删除
win.removeIfPossible();
}
}
- 关键对象:
WindowState是WMS中窗口的完整档案(含大小、位置、Surface等)
4. 安全检查与延迟删除(removeIfPossible())
java
Copy
// 代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowState.java
private void removeIfPossible(boolean keepVisibleDeadWindow) {
// 多种情况需延迟删除(返回不执行):
// 情况1:窗口正在执行动画 → 等动画播完
// 情况2:窗口正在处理事件 → 等事件结束
// 情况3:窗口是Keyguard锁屏 → 等解锁后
if (shouldDelayRemoval()) return;
// 满足条件立即删除
removeImmediately();
}
- 延迟场景:类似"店铺关门前要等最后一位顾客离开"
5. 正式注销与资源释放(removeImmediately())
java
Copy
@Override
void removeImmediately() {
if (mRemoved) return; // 防重删
mRemoved = true; // 标记已删除
// 1. 从策略模块移除(如状态栏控制器)
mPolicy.removeWindowLw(this);
// 2. 关闭门铃系统(输入通道)
disposeInputChannel();
// 3. 拆除玻璃橱窗(释放Surface)
mWinAnimator.destroySurfaceLocked();
// 4. 清理营业执照(Session关联)
mSession.windowRemovedLocked();
// 5. 解除死亡监控
mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
// 6. 集中清理资源
mService.postWindowRemoveCleanupLocked(this);
}
-
关键操作:
-
disposeInputChannel():关闭输入事件通道(类似拆掉门铃) -
destroySurfaceLocked():释放Surface(类似拆除玻璃橱窗) -
windowRemovedLocked():减少Session计数,若计数归零则释放整个Session
-
📊 流程全景图
⚠️ 关键注意事项
-
线程安全:
java Copy void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException("Only the original thread..."); } }- 必须在创建窗口的线程调用删除(否则抛异常)
-
延迟删除场景:
- 窗口动画进行中(
mWinAnimator.isAnimating()) - 窗口正在处理触摸事件(
mHandleByInput) - 系统关键窗口(如Keyguard)
- 窗口动画进行中(
-
资源释放顺序:
💡 总结:窗口删除四部曲
- 申请阶段:App调用
removeView()触发流程 - 本地清理:移除View树、释放本地资源
- 跨进程通知:通过
IWindowSession通知WMS - WMS注销:安全检查 → 释放Surface → 清理WindowState
通过这个严谨的流程,Android系统确保了:
- 窗口资源不会泄露(Surface/InputChannel)
- 删除过程线程安全
- 系统稳定性(避免误删关键窗口)
- 用户体验(动画/事件未完成时不突兀关闭)