悬浮窗与权限管理:SYSTEM_ALERT_WINDOW 权限、悬浮窗的创建限制、Android 不同版本的策略变化

悬浮窗,说白了就是能「飘」在其他应用上面的窗口。这个功能在 Android 里一直是个敏感话题。为什么?因为它太容易被滥用了。我见过不少应用,明明就是个手电筒,结果也要申请悬浮窗权限,然后给你弹一堆广告。嗯,Google 也意识到了这个问题,所以每个版本都在收紧策略。

SYSTEM_ALERT_WINDOW 权限的前世今生

这个权限的全称是 android.permission.SYSTEM_ALERT_WINDOW。它允许你的窗口显示在其他应用之上。听起来很强大对吧?但它的获取方式在不同版本里完全不一样。

核心要点:SYSTEM_ALERT_WINDOW 是一个 signature|privileged 级别的权限。普通应用想拿到它,必须走「用户手动授权」这条路。

我记得在 Android 4.x 时代,只要在 Manifest 里声明一下,应用就能直接使用悬浮窗。那时候真的是「野蛮生长」——各种悬浮球、加速球、清理球满天飞。后来到了 Android 6.0,Google 终于坐不住了,把它改成了运行时权限。但有意思的是,它不像其他运行时权限那样弹个对话框就行,而是需要用户去「设置」里手动打开开关。

为什么会这样?我个人猜测,Google 是想增加一道门槛——让用户明确知道「这个应用要在你屏幕上乱飘了」。你想想看,如果只是弹个对话框,很多人看都不看就点「允许」了。但让你去设置里翻半天找到开关,至少你会多看一眼。

悬浮窗的创建限制

就算拿到了权限,也不是你想怎么创建就怎么创建的。这里有几个硬性限制,我在项目中踩过不少坑。

窗口类型的选择

创建悬浮窗时,你必须指定窗口类型。常用的有:

类型 API 级别 说明
TYPE_PHONE 1+ 电话窗口,需要 SYSTEM_ALERT_WINDOW 权限
TYPE_PRIORITY_PHONE 1+ 优先级更高的电话窗口
TYPE_SYSTEM_ALERT 1+ 系统警告窗口,同样需要权限
TYPE_APPLICATION_OVERLAY 26+ 应用覆盖层,Android 8.0 之后推荐使用

注意:从 Android 8.0(API 26)开始,TYPE_PHONETYPE_SYSTEM_ALERT 已经被标记为过时。你必须使用 TYPE_APPLICATION_OVERLAY。我曾经在适配 8.0 时没注意这个变化,结果悬浮窗直接不显示,查了半天才发现是类型问题。

权限检查与申请

在创建悬浮窗前,一定要检查权限。代码大概是这样的:

// 检查权限
if (Settings.canDrawOverlays(context)) {
    // 已经有权限,直接创建
    createFloatingWindow();
} else {
    // 没有权限,引导用户去设置
    Intent intent = new Intent(
        Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
        Uri.parse("package:" + context.getPackageName())
    );
    startActivityForResult(intent, REQUEST_CODE);
}

这里有个坑:Settings.canDrawOverlays() 返回的是当前状态,但用户可能在设置里关闭了权限。所以最好在 onResume() 里重新检查一下。我之前有个项目,用户关闭权限后悬浮窗消失了,但应用没有及时检测到,结果出现了空指针异常。

Android 不同版本的策略变化

这个变化过程,说白了就是一部「Google 与流氓应用斗智斗勇」的历史。我按版本梳理一下:

Android 6.0(API 23)

悬浮窗变成了运行时权限。但申请方式很特殊——不能直接弹对话框,必须跳转到设置页面。很多用户根本找不到开关在哪。

Android 8.0(API 26)

引入了 TYPE_APPLICATION_OVERLAY。这个类型的窗口会自动处理一些事情,比如:

  • 窗口不会遮挡状态栏和导航栏
  • 点击窗口外的区域,事件会自动穿透
  • 系统会限制窗口的尺寸和位置

嗯,这里要注意:TYPE_APPLICATION_OVERLAY 的窗口默认是「不可点击」的。如果你需要处理触摸事件,必须设置 FLAG_NOT_TOUCH_MODALFLAG_WATCH_OUTSIDE_TOUCH 这两个标志位。

Android 10(API 29)

Google 开始限制后台启动 Activity。但悬浮窗不受影响——前提是你已经拿到了权限。我记得当时有个项目,用户反馈说「点了悬浮窗没反应」,排查后发现是后台启动限制导致的。解决方案是:在点击悬浮窗时,先通过 Notification 启动一个前台服务,再通过前台服务启动 Activity。

Android 11(API 30)

权限申请变得更严格了。应用必须声明 QUERY_ALL_PACKAGES 权限才能查询其他应用的信息。另外,Settings.ACTION_MANAGE_OVERLAY_PERMISSION 这个 Intent 的行为也变了——它不再直接跳转到你的应用详情页,而是跳转到「特殊权限」列表。用户需要手动找到你的应用,再打开开关。

避坑指南:我曾经在 Android 11 上遇到一个问题:用户明明在设置里打开了悬浮窗权限,但 Settings.canDrawOverlays() 还是返回 false。后来发现是用户打开了「显示在其他应用上层」的开关,但系统还需要用户确认一个弹窗。所以,申请权限后最好延迟 1-2 秒再检查状态。

知识体系图

下面这张图总结了悬浮窗与权限管理的核心逻辑:

悬浮窗与权限管理知识体系 悬浮窗 SYSTEM_ALERT_WINDOW 权限 创建限制 版本策略变化 检查:Settings.canDrawOverlays() 申请:ACTION_MANAGE_OVERLAY_PERMISSION 窗口类型:TYPE_APPLICATION_OVERLAY 标志位:FLAG_NOT_TOUCH_MODAL Android 6.0:运行时权限 Android 8.0:新窗口类型 Android 11:权限申请变化 核心原则:权限是基础,类型要选对,版本要适配

实战中的一些建议

说了这么多,最后分享几个我在实际项目中的经验:

  • 不要滥用悬浮窗——用户真的会反感。我见过一个应用,每次打开都弹悬浮窗,结果被用户直接卸载了。
  • 做好权限被拒绝的兜底——用户可能永远都不会打开权限。你的应用应该能在没有悬浮窗的情况下正常工作。
  • 测试要覆盖多个版本——特别是 Android 8.0 和 Android 11,这两个版本的变化最大。我建议至少在这两个版本上做完整的功能测试。
  • 考虑用户体验——悬浮窗的尺寸不要太大,位置不要遮挡关键内容。最好提供拖动和关闭功能。

一句话总结:悬浮窗权限管理,说白了就是「权限是门槛,类型是规范,版本是变量」。把这三件事搞清楚了,你的悬浮窗功能就能稳稳地跑起来。

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