第一章:诡异的现场
小李最近遇到了一件怪事。他在开发一个系统工具类 App 时,给 Dialog 加了一行代码:
java
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
结果发现,当他按 Home 键把 App 切到后台,甚至手动 finish 掉 Activity 时,这个 Dialog 竟然像幽灵一样留在屏幕上!
"这 Dialog 成精了?" 小李盯着屏幕喃喃自语。普通 Dialog 只要 Activity 一销毁就会跟着消失,这个加了特殊 "咒语" 的 Dialog,到底发生了什么?
第二章:侦探登场 ------ 对比实验
为了破解谜团,我们做了两组实验,用代码记录下 Dialog 的 "行为轨迹"。
实验 A:普通 Dialog(乖乖听话型)
java
// 普通Dialog的创建过程
Dialog normalDialog = new Dialog(MainActivity.this);
normalDialog.setContentView(R.layout.dialog_normal);
normalDialog.setTitle("我是乖宝宝");
// 显示Dialog
showButton.setOnClickListener(v -> normalDialog.show());
// 销毁Activity的按钮
destroyButton.setOnClickListener(v -> {
MainActivity.this.finish(); // 销毁Activity
Log.d("Dialog日志", "Activity已销毁");
});
实验现象:点击销毁按钮后,Activity 消失的同时,普通 Dialog 也跟着不见了。
实验 B:系统级 Dialog(叛逆型)
java
// 系统级Dialog的创建过程
Dialog systemDialog = new Dialog(MainActivity.this);
systemDialog.setContentView(R.layout.dialog_system);
systemDialog.setTitle("我要自由");
// 关键咒语:设置系统级窗口类型
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
systemDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
} else {
systemDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
}
// 显示Dialog(需要先申请悬浮窗权限)
showSystemButton.setOnClickListener(v -> {
if (Settings.canDrawOverlays(MainActivity.this)) {
systemDialog.show();
}
});
// 同样的销毁按钮
destroyButton.setOnClickListener(v -> {
MainActivity.this.finish();
Log.d("Dialog日志", "Activity已销毁");
});
实验现象:点击销毁按钮后,Activity 消失了,但系统级 Dialog 依然霸占着屏幕,甚至能在其他 App 上显示!
第三章:线索追踪 ------ 谁在管理 Dialog?
我们把 Android 系统想象成一个大型公寓楼:
-
Activity 是一个个住户房间
-
普通 Dialog 是房间里的家具(衣柜、桌子),必须放在房间里
-
WindowManagerService(WMS) 是公寓管理员,负责管理所有房间和公共区域
-
TYPE_APPLICATION_OVERLAY 是 "公共区域通行证"
普通 Dialog 的 "户口" 挂在 Activity 名下(通过 Context 关联),就像家具的所有权属于房间主人。当房间被拆除(Activity 销毁),管理员 WMS 会通知家具一起被清走。
但加上TYPE_APPLICATION_OVERLAY
后,Dialog 相当于办理了 "户口迁移":
- 它从 "房间私有财产" 变成了 "公寓公共设施"(比如楼道里的公告栏)
- 管理权从房间主人(Activity)移交到了公寓管理员(WMS)
- 即使原来的房间拆了(Activity 销毁),只要管理员没收到拆除通知,公告栏就会一直存在
第四章:底层原理 ------ 窗口的 "身份牌"
Android 中所有可见元素都是 "窗口"(Window),每个窗口都有唯一的type
属性,这相当于窗口的 "身份牌":
身份牌类型 | 归属权 | 生命周期特点 |
---|---|---|
普通应用窗口(默认) | 所属 Activity | 随 Activity 销毁而销毁 |
TYPE_APPLICATION_OVERLAY | 系统 WMS | 独立存在,需手动销毁 |
当设置TYPE_APPLICATION_OVERLAY
后:
- Dialog 的窗口被纳入系统窗口管理体系
- 它的生命周期不再与创建它的 Activity 绑定
- 只有调用
dialog.dismiss()
或系统重启时,WMS 才会销毁这个窗口 - 这也是为什么需要申请
SYSTEM_ALERT_WINDOW
权限 ------ 系统要限制这种 "不受控" 的窗口
第五章:真相大白
Dialog 不消失的秘密,其实是一场成功的 "权限升级":
-
普通 Dialog 是 Activity 的 "附属品",主人不在了它就必须离开
-
系统级 Dialog 通过
TYPE_APPLICATION_OVERLAY
获得了 "独立居住权",成为系统直接管理的 "常住居民"
就像现实中:如果你在自己店里挂海报(普通 Dialog),店铺关门海报就得取下;但如果你在商场公共区域挂广告牌(系统级窗口),即使你店铺关门了,广告牌只要没到期就会一直挂着。
破案总结
要让系统级 Dialog 乖乖听话,必须手动 "遣散" 它:
java
// 在Activity销毁前,主动销毁系统级Dialog
@Override
protected void onDestroy() {
super.onDestroy();
if (systemDialog != null && systemDialog.isShowing()) {
systemDialog.dismiss();
}
}
从此,小李的 Dialog 再也不会 "闹鬼" 了。这个故事告诉我们:在 Android 世界里,权限决定归属,归属决定命运。