15. WMS 中的权限管理:SYSTEM_ALERT_WINDOW 权限的申请与校验
聊到 WMS 的权限管理,SYSTEM_ALERT_WINDOW 绝对是个绕不开的话题。说白了,它就是那个让你能「悬浮在其他应用之上」的权限——比如手机管家的小悬浮窗、微信视频通话的悬浮小窗。这个权限如果管不好,整个系统的窗口秩序就乱套了。
我当年刚接触 Android 系统时,就吃过这个权限的亏。有一次做 ROM 定制,发现某个第三方应用疯狂弹悬浮窗,用户怎么关都关不掉。后来一查,原来是 SYSTEM_ALERT_WINDOW 权限校验被绕过了。嗯,从那以后,我对 WMS 的权限管理就格外上心。
15.1 权限的本质:谁能在 WMS 里「插队」
WMS 管理着所有窗口的层级。普通应用的窗口,层级范围是有限的。但有了 SYSTEM_ALERT_WINDOW,窗口就能跑到 TYPE_APPLICATION_OVERLAY 这个层级。这个层级在状态栏和输入法之下,但在普通应用窗口之上。
你想想看,这相当于给了应用一个「VIP 通道」。所以系统必须严格把关。
核心结论:SYSTEM_ALERT_WINDOW 权限的本质,是允许应用创建窗口类型为 TYPE_APPLICATION_OVERLAY(API 26+)或 TYPE_SYSTEM_ALERT(旧版本)的窗口。这些窗口的 Z-order 高于普通应用窗口。
15.2 权限申请流程:从 Manifest 到运行时
申请这个权限,分两步走。第一步是在 AndroidManifest.xml 里声明:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
第二步,从 Android 6.0(API 23)开始,这个权限属于 特殊权限。它不像普通危险权限那样弹个对话框就行。用户必须手动去「设置 → 应用 → 特殊权限 → 显示在其他应用上层」里开启。
我记得在 Android 8.0 之前,很多应用通过反射或者隐式 Intent 直接跳转到权限设置页。后来 Google 收紧了策略,现在推荐用 Settings.ACTION_MANAGE_OVERLAY_PERMISSION 这个 Intent Action。
代码里怎么判断权限是否授予?用这个:
if (Settings.canDrawOverlays(context)) {
// 权限已授予,可以创建悬浮窗
} else {
// 权限未授予,引导用户去设置
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + context.getPackageName()));
startActivity(intent);
}
我的习惯:在调用 canDrawOverlays() 之前,先检查一下 API 版本。因为不同版本的行为略有差异。比如在 Android 10 上,即使权限被撤销,已经创建的悬浮窗也不会立刻消失,需要应用主动销毁。
15.3 WMS 中的权限校验:源码级别的把关
当应用调用 WindowManager.addView() 时,WMS 会进行一系列校验。核心代码在 WindowManagerService.java 的 addWindow() 方法里。
我简化一下关键逻辑:
// WindowManagerService.java (简化版)
public int addWindow(Session session, IWindow client, ...) {
// ... 省略其他校验
// 检查窗口类型是否需要特殊权限
if (type >= FIRST_SYSTEM_WINDOW && type < LAST_SYSTEM_WINDOW) {
// 系统窗口类型,需要 SYSTEM_ALERT_WINDOW 权限
if (!checkCallingPermission(SYSTEM_ALERT_WINDOW, ...)) {
throw new SecurityException("...");
}
}
// 对于 TYPE_APPLICATION_OVERLAY (API 26+)
if (type == TYPE_APPLICATION_OVERLAY) {
if (!Settings.canDrawOverlays(context)) {
// 权限未授予,拒绝添加窗口
return WindowManagerGlobal.ADD_PERMISSION_DENIED;
}
}
// ... 继续其他校验
}
这里有个细节。对于 API 26 以上的设备,Google 引入了 TYPE_APPLICATION_OVERLAY 来替代旧的 TYPE_SYSTEM_ALERT。新的窗口类型校验更严格——它直接调用 Settings.canDrawOverlays() 来检查运行时权限状态。
而对于旧的系统窗口类型(比如 TYPE_SYSTEM_ALERT),WMS 检查的是调用进程是否持有 SYSTEM_ALERT_WINDOW 权限。这两种校验路径不一样,但最终目的相同。
避坑指南:我曾经遇到过一个案例——某个应用在 Android 11 上使用 TYPE_SYSTEM_ALERT 创建悬浮窗,结果被系统直接拒绝了。因为从 Android 10 开始,非系统应用已经不能使用 TYPE_SYSTEM_ALERT 了。必须改用 TYPE_APPLICATION_OVERLAY。如果你还在用旧类型,赶紧改过来。
15.4 权限校验的完整流程
为了让你更直观地理解,我画了一张流程图。它展示了从应用请求创建悬浮窗,到 WMS 最终决定是否放行的完整路径。
15.5 权限校验的细节与陷阱
上面这张图看起来简单,但实际代码里有很多细节。我挑几个关键点说说。
15.5.1 权限检查的时机
WMS 并不是在 addWindow() 一开始就检查权限。它先检查窗口类型是否合法,然后检查调用进程是否有对应的权限。如果类型不合法,根本不会走到权限检查那一步。
我记得有一次调试,发现某个应用创建窗口时总是报权限错误。但明明已经在设置里开了权限。后来发现,它用的是 TYPE_TOAST 类型。这个类型在 Android 10 之后也被限制了,需要 SYSTEM_ALERT_WINDOW 权限。但它的错误提示并不明确,容易让人误解。
15.5.2 多进程场景下的权限校验
如果你的应用使用了多进程,比如通过 android:process 属性声明了远程进程。那么 WMS 校验权限时,检查的是 调用进程 的权限,而不是主进程的。
这意味着,如果你在子进程里创建悬浮窗,子进程必须也拥有 SYSTEM_ALERT_WINDOW 权限。但权限授予是针对整个应用的,所以理论上没问题。不过,我遇到过一种情况——子进程通过 Context.createPackageContext() 获取了另一个应用的上下文,然后用那个上下文创建窗口。这时候 WMS 检查的是另一个应用的权限。嗯,这是个坑。
我的建议:尽量在主进程创建和管理悬浮窗。如果必须在子进程操作,确保子进程的 Context 来源正确,不要跨包获取 Context。
15.6 权限的动态变化与窗口生命周期
用户可以在设置里随时关闭 SYSTEM_ALERT_WINDOW 权限。这时候,已经创建的悬浮窗怎么办?
答案是:系统不会自动移除。WMS 不会监听权限变化事件,也不会主动销毁窗口。但应用如果尝试添加新的窗口,就会被拒绝。
我建议你在 onResume() 或窗口显示时,重新检查一次权限。如果权限被撤销,主动移除悬浮窗。否则用户可能会看到「残留」的悬浮窗,但无法交互——因为点击事件会被系统拦截。
@Override
protected void onResume() {
super.onResume();
if (!Settings.canDrawOverlays(this)) {
// 权限被撤销,移除悬浮窗
removeFloatingWindow();
// 提示用户
Toast.makeText(this, "悬浮窗权限已关闭", Toast.LENGTH_SHORT).show();
}
}
15.7 总结
SYSTEM_ALERT_WINDOW 权限的管理,是 WMS 中一个非常典型的权限控制场景。它涉及权限声明、运行时检查、窗口类型匹配等多个环节。理解了这个流程,你就能明白为什么有些悬浮窗能显示,有些却被系统默默拒绝了。
最后提醒一句:不要滥用这个权限。即使用户授予了,频繁弹出悬浮窗也会严重影响用户体验。我在做系统优化时,见过太多因为滥用悬浮窗而被用户卸载的应用。权限是双刃剑,用好了是功能,用不好就是骚扰。
核心要点回顾:
- SYSTEM_ALERT_WINDOW 是特殊权限,需要用户手动在设置中开启
- API 26+ 推荐使用 TYPE_APPLICATION_OVERLAY 窗口类型
- WMS 在 addWindow() 中校验权限,不同窗口类型走不同校验路径
- 权限被撤销后,已创建的窗口不会自动移除,需要应用主动处理
- 多进程场景下,注意 Context 的来源
公众号:蓝海资料掘金营,微信deep3321