悬浮窗与权限管理: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_PHONE 和 TYPE_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_MODAL 和 FLAG_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 秒再检查状态。
知识体系图
下面这张图总结了悬浮窗与权限管理的核心逻辑:
实战中的一些建议
说了这么多,最后分享几个我在实际项目中的经验:
- 不要滥用悬浮窗——用户真的会反感。我见过一个应用,每次打开都弹悬浮窗,结果被用户直接卸载了。
- 做好权限被拒绝的兜底——用户可能永远都不会打开权限。你的应用应该能在没有悬浮窗的情况下正常工作。
- 测试要覆盖多个版本——特别是 Android 8.0 和 Android 11,这两个版本的变化最大。我建议至少在这两个版本上做完整的功能测试。
- 考虑用户体验——悬浮窗的尺寸不要太大,位置不要遮挡关键内容。最好提供拖动和关闭功能。
一句话总结:悬浮窗权限管理,说白了就是「权限是门槛,类型是规范,版本是变量」。把这三件事搞清楚了,你的悬浮窗功能就能稳稳地跑起来。