通俗易懂的讲解:Android窗口删除全过程

86 阅读4分钟

我将用"窗口管理局"的比喻,结合源码解析这篇文章的核心内容。整个过程就像关闭一家店铺(删除窗口),需要经过本地清理和中央注销两大阶段:


🏢 第一章:本地清理准备(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

📊 流程全景图

image.png

⚠️ 关键注意事项

  1. ​线程安全​​:

    java
    Copy
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException("Only the original thread...");
        }
    }
    
    • 必须在创建窗口的线程调用删除(否则抛异常)
  2. ​延迟删除场景​​:

    • 窗口动画进行中(mWinAnimator.isAnimating()
    • 窗口正在处理触摸事件(mHandleByInput
    • 系统关键窗口(如Keyguard)
  3. ​资源释放顺序​​:

image.png


💡 总结:窗口删除四部曲

  1. ​申请阶段​​:App调用removeView()触发流程
  2. ​本地清理​​:移除View树、释放本地资源
  3. ​跨进程通知​​:通过IWindowSession通知WMS
  4. ​WMS注销​​:安全检查 → 释放Surface → 清理WindowState

通过这个严谨的流程,Android系统确保了:

  • 窗口资源不会泄露(Surface/InputChannel)
  • 删除过程线程安全
  • 系统稳定性(避免误删关键窗口)
  • 用户体验(动画/事件未完成时不突兀关闭)