一、WindowManager 常见问题与避坑指南
说实话,WindowManager 这块坑不少。我做了这么多年 Android 系统开发,踩过的坑能写满一本小册子。今天我把最常见的几类问题整理出来,希望能帮你少走弯路。
1. 窗口泄漏:最隐蔽的内存杀手
窗口泄漏,说白了就是窗口已经不用了,但 WindowManager 还持有它的引用。这个问题在 Activity 销毁时特别容易发生。
我曾经在一个直播项目中遇到过:用户退出直播间,但悬浮窗还在。结果内存暴涨,最后 OOM 了。查了半天,发现是 onDestroy 里忘了调用 removeView。
// ❌ 错误写法
public class MainActivity extends AppCompatActivity {
private View mFloatingView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFloatingView = LayoutInflater.from(this).inflate(R.layout.floating, null);
WindowManager wm = getSystemService(WindowManager.class);
wm.addView(mFloatingView, params); // 添加了,但没考虑移除
}
// 忘记在 onDestroy 中移除
}
// ✅ 正确写法
@Override
protected void onDestroy() {
super.onDestroy();
if (mFloatingView != null && mFloatingView.isAttachedToWindow()) {
WindowManager wm = getSystemService(WindowManager.class);
wm.removeView(mFloatingView);
mFloatingView = null;
}
}
2. 窗口类型选择错误:系统弹窗的雷区
窗口类型选错了,轻则弹不出来,重则直接崩溃。我见过太多新手在这上面栽跟头。
| 窗口类型 | 适用场景 | 注意事项 |
|---|---|---|
| TYPE_APPLICATION | 应用内弹窗 | 跟随 Activity 生命周期 |
| TYPE_APPLICATION_OVERLAY | 悬浮窗(Android 8.0+) | 需要 SYSTEM_ALERT_WINDOW 权限 |
| TYPE_TOAST | 短暂提示 | Android 10 后限制较多 |
| TYPE_KEYGUARD_DIALOG | 锁屏弹窗 | 系统级权限,普通应用别碰 |
嗯,这里要注意:TYPE_SYSTEM_ALERT 在 Android 8.0 之后已经被废弃了。如果你还在用,赶紧换成 TYPE_APPLICATION_OVERLAY。
3. 权限问题:悬浮窗权限的坑
悬浮窗权限,也就是 SYSTEM_ALERT_WINDOW,是很多功能的门槛。我记得有个项目,用户反馈悬浮窗不显示,结果发现是权限没开。
// 检查悬浮窗权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
// 引导用户去设置页面开启
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
}
你想想看,用户如果拒绝了这个权限,你的悬浮窗功能就完全废了。所以我的建议是:在功能入口处就做好权限检测,别等到用户操作到一半才弹提示。
4. 多线程操作风险:WindowManager 不是线程安全的
这个问题很隐蔽。WindowManager 的操作必须在主线程执行。如果你在子线程里 addView 或 removeView,大概率会崩溃。
// ❌ 错误写法:子线程操作 WindowManager
new Thread(() -> {
WindowManager wm = getSystemService(WindowManager.class);
wm.addView(view, params); // 这里会抛异常
}).start();
// ✅ 正确写法:切回主线程
new Handler(Looper.getMainLooper()).post(() -> {
WindowManager wm = getSystemService(WindowManager.class);
wm.addView(view, params);
});
为什么会这样?因为 WindowManager 内部持有的是主线程的 Looper。你从子线程发消息,它根本收不到。说白了,这是 Android 的 UI 线程模型决定的。
5. 兼容性适配:碎片化是绕不过的坎
Android 碎片化严重,不同厂商、不同版本对 WindowManager 的支持都不一样。我整理了几个常见问题:
- 华为/荣耀: 对悬浮窗权限管控严格,需要额外适配
- 小米: 部分机型需要开启「显示悬浮窗」开关
- OPPO/vivo: 锁屏弹窗需要特殊权限
- Android 10+: 后台启动 Activity 受限,弹窗逻辑要调整
我记得有一次,在华为 Mate 40 上测试悬浮窗,怎么都弹不出来。后来发现是华为的「悬浮窗权限」和「应用内弹窗权限」是分开的。嗯,这种坑只能靠真机测试来发现。
- 在主流厂商的真机上做兼容性测试
- 使用 Build.MANUFACTURER 判断厂商,做针对性适配
- 降级方案:如果悬浮窗不行,就改用 Toast 或 Snackbar
知识体系总览
下面这张图总结了 WindowManager 常见问题的核心脉络:
这张图把五大问题串联起来了。你会发现,它们之间其实有内在联系——比如权限问题可能导致窗口泄漏,类型选择错误又和兼容性适配相关。所以,处理时要系统性地看问题。
公众号:蓝海资料掘金营,微信 deep3321