21、系统级悬浮窗:TYPE_SYSTEM_ALERT 与 TYPE_APPLICATION_OVERLAY 的区别
悬浮窗,这个功能在 Android 开发中一直是个「又爱又恨」的存在。爱它,是因为它能实现很多酷炫的功能——比如手机管家的悬浮小球、视频通话的悬浮小窗、录屏工具的控制面板。恨它,是因为权限问题、碎片化适配、还有 Google 不断收紧的政策。
今天我们就来聊聊两个最关键的悬浮窗类型:TYPE_SYSTEM_ALERT 和 TYPE_APPLICATION_OVERLAY。这两个东西,说白了就是 Android 系统在不同时期给出的两种「悬浮窗通行证」。
一句话总结:TYPE_SYSTEM_ALERT 是老牌王者,权限高但已被限制;TYPE_APPLICATION_OVERLAY 是新生代,更安全但功能受限。
一、它们到底是什么?
先看官方定义。
TYPE_SYSTEM_ALERT 是 Android 早期就存在的窗口类型。它的优先级非常高,几乎可以覆盖所有界面——包括状态栏、锁屏界面、甚至其他应用的 Activity。我早期做手机管家项目时,用的就是它来实现悬浮小球。
TYPE_APPLICATION_OVERLAY 是 Android 8.0(API 26)引入的新类型。Google 官方明确说:从 8.0 开始,请使用这个类型代替 TYPE_SYSTEM_ALERT。它的窗口层级低于系统 UI,但高于普通应用。
嗯,这里要注意:虽然官方推荐用新的,但老项目里 TYPE_SYSTEM_ALERT 依然大量存在。你想想看,一个已经跑了好几年的 App,说改就改?没那么简单。
二、核心区别对比
| 对比维度 | TYPE_SYSTEM_ALERT | TYPE_APPLICATION_OVERLAY |
|---|---|---|
| 引入版本 | Android 1.0 | Android 8.0 (API 26) |
| 窗口层级 | 极高,可覆盖系统 UI | 较高,但低于系统 UI |
| 权限要求 | SYSTEM_ALERT_WINDOW | SYSTEM_ALERT_WINDOW |
| 触摸事件 | 默认可接收 | 默认可接收 |
| 锁屏显示 | 可以 | 不可以 |
| 状态栏覆盖 | 可以 | 不可以 |
| Google Play 政策 | 严格限制 | 推荐使用 |
| 兼容性 | 老版本兼容好 | 新版本兼容好 |
这张表其实已经把核心差异说清楚了。但我还是想展开聊聊几个关键点。
三、窗口层级:谁在上面?
Android 的窗口系统是一个 Z-Order 栈。每个窗口类型都有固定的层级范围。我画了一张图,帮你直观理解:
从这张图可以看得很清楚:TYPE_SYSTEM_ALERT 在系统 UI 之下、普通应用之上,但它可以覆盖系统 UI。而 TYPE_APPLICATION_OVERLAY 则老老实实待在系统 UI 下面。
为什么会这样?说白了,Google 觉得普通 App 不应该有能力覆盖状态栏和锁屏——这太危险了。万一有个恶意 App 搞个假的状态栏,用户点进去就中招了。
四、权限与适配
两个类型都需要 SYSTEM_ALERT_WINDOW 权限。但获取方式有区别。
在 Android 6.0 之前,只要在 Manifest 里声明就行。6.0 之后,需要用户手动在「设置-特殊权限-显示在其他应用上层」中开启。我遇到过不少用户反馈:「为什么我的悬浮窗不显示?」——十有八九是没开权限。
我的建议:在代码中检测权限时,不要只检查 Settings.canDrawOverlays(),还要考虑不同厂商的定制系统。比如小米、华为、OPPO 都有自己的悬浮窗权限管理,光靠系统 API 是不够的。
代码示例:
// 检查悬浮窗权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(context)) {
// 引导用户去开启权限
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + context.getPackageName()));
startActivity(intent);
}
}
// 创建悬浮窗(推荐方式)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Android 8.0+ 使用 TYPE_APPLICATION_OVERLAY
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
// 老版本使用 TYPE_SYSTEM_ALERT
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
五、避坑指南
我曾经在一个项目中踩过一个大坑:在 Android 10 上使用 TYPE_SYSTEM_ALERT 创建悬浮窗,结果发现触摸事件完全失效。查了半天才发现,Google 在 Android 10 中限制了 TYPE_SYSTEM_ALERT 的触摸事件传递。
还有几个常见的坑:
- 锁屏显示问题:TYPE_APPLICATION_OVERLAY 在锁屏状态下不可见。如果你需要做锁屏上的悬浮窗(比如音乐控制),只能用 TYPE_SYSTEM_ALERT,但 Google Play 可能拒绝上架。
- 输入法遮挡:TYPE_APPLICATION_OVERLAY 的窗口默认在输入法下面。如果你需要悬浮窗在输入法之上,需要设置
layoutParams.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM。 - 分屏模式:在分屏模式下,TYPE_APPLICATION_OVERLAY 的行为可能不符合预期。我建议在分屏时隐藏悬浮窗,或者缩小到角落。
重要警告:从 Android 14 开始,Google 进一步收紧了悬浮窗权限。即使你申请了 SYSTEM_ALERT_WINDOW 权限,也可能无法在所有场景下正常显示。建议密切关注官方文档更新。
六、实际项目中的选择策略
我个人习惯这样选择:
- 新项目(minSdk >= 26):只用 TYPE_APPLICATION_OVERLAY。省心、合规、未来兼容性好。
- 老项目适配:根据 API 版本做分支判断。API 26+ 用新的,以下用旧的。
- 特殊场景(如锁屏控制):评估是否真的需要。如果必须,做好被拒的心理准备,或者走企业分发渠道。
你想想看,Google 为什么要推 TYPE_APPLICATION_OVERLAY?说白了就是为了安全。老版本的 TYPE_SYSTEM_ALERT 权限太大,容易被滥用。作为开发者,我们也要跟上平台的步伐。
最后说一句:悬浮窗功能虽然好用,但不要滥用。用户手机上悬浮窗太多,体验真的很差。我自己的原则是——能不用就不用,用了就要给用户提供关闭选项。
公众号:蓝海资料掘金营,微信deep3321