一、WindowManager 常见问题与避坑指南

说实话,WindowManager 这块坑不少。我做了这么多年 Android 系统开发,踩过的坑能写满一本小册子。今天我把最常见的几类问题整理出来,希望能帮你少走弯路。

1. 窗口泄漏:最隐蔽的内存杀手

窗口泄漏,说白了就是窗口已经不用了,但 WindowManager 还持有它的引用。这个问题在 Activity 销毁时特别容易发生。

⚠️ 典型场景: Activity 被销毁了,但你通过 WindowManager 添加的 View 还在屏幕上。

我曾经在一个直播项目中遇到过:用户退出直播间,但悬浮窗还在。结果内存暴涨,最后 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;
    }
}
💡 我的习惯: 每次 addView 之后,立刻在 onDestroy 或 onDetachedFromWindow 里写好 removeView。这叫「配对编程」——添加和移除成对出现。

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。

🔑 核心原则: 能用 TYPE_APPLICATION 就别用系统类型。系统类型意味着系统级权限,审核时会很麻烦。

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);
    }
}

你想想看,用户如果拒绝了这个权限,你的悬浮窗功能就完全废了。所以我的建议是:在功能入口处就做好权限检测,别等到用户操作到一半才弹提示。

⚠️ 注意: Android 11 之后,如果用户多次拒绝权限,系统会直接标记为「不再询问」。这时候你需要引导用户去系统设置里手动开启。

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 线程模型决定的。

💡 我的经验: 如果你用 Kotlin,可以用 lifecycleScope 或 viewModelScope 配合 withContext(Dispatchers.Main) 来确保主线程执行。

5. 兼容性适配:碎片化是绕不过的坎

Android 碎片化严重,不同厂商、不同版本对 WindowManager 的支持都不一样。我整理了几个常见问题:

  • 华为/荣耀: 对悬浮窗权限管控严格,需要额外适配
  • 小米: 部分机型需要开启「显示悬浮窗」开关
  • OPPO/vivo: 锁屏弹窗需要特殊权限
  • Android 10+: 后台启动 Activity 受限,弹窗逻辑要调整

我记得有一次,在华为 Mate 40 上测试悬浮窗,怎么都弹不出来。后来发现是华为的「悬浮窗权限」和「应用内弹窗权限」是分开的。嗯,这种坑只能靠真机测试来发现。

🔑 适配建议:
  1. 在主流厂商的真机上做兼容性测试
  2. 使用 Build.MANUFACTURER 判断厂商,做针对性适配
  3. 降级方案:如果悬浮窗不行,就改用 Toast 或 Snackbar

知识体系总览

下面这张图总结了 WindowManager 常见问题的核心脉络:

WindowManager 常见问题体系 窗口泄漏 类型选择错误 权限问题 多线程操作风险 兼容性适配 解决方案与最佳实践 • 配对编程:addView 与 removeView 成对出现 • 类型选择:优先 TYPE_APPLICATION,避免系统类型 • 权限检测:功能入口处提前检查,做好降级方案 • 主线程操作:使用 Handler 或协程确保主线程 • 真机测试:覆盖主流厂商和 Android 版本

这张图把五大问题串联起来了。你会发现,它们之间其实有内在联系——比如权限问题可能导致窗口泄漏,类型选择错误又和兼容性适配相关。所以,处理时要系统性地看问题。

💡 最后说一句: WindowManager 的坑虽然多,但只要你掌握了「配对移除」「主线程操作」「权限前置」这三个原则,大部分问题都能避免。我在项目中就是这么做的,效果还不错。

公众号:蓝海资料掘金营,微信 deep3321