21、系统级悬浮窗:TYPE_SYSTEM_ALERT 与 TYPE_APPLICATION_OVERLAY 的区别

悬浮窗,这个功能在 Android 开发中一直是个「又爱又恨」的存在。爱它,是因为它能实现很多酷炫的功能——比如手机管家的悬浮小球、视频通话的悬浮小窗、录屏工具的控制面板。恨它,是因为权限问题、碎片化适配、还有 Google 不断收紧的政策。

今天我们就来聊聊两个最关键的悬浮窗类型:TYPE_SYSTEM_ALERTTYPE_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 栈。每个窗口类型都有固定的层级范围。我画了一张图,帮你直观理解:

Android 窗口层级示意图(Z-Order) 系统级窗口(状态栏、导航栏、锁屏) ▼ 更高层级 TYPE_SYSTEM_ALERT(可覆盖系统UI) TYPE_APPLICATION_OVERLAY(高于应用,低于系统) 普通应用窗口(Activity) ▼ 更低层级 壁纸层

从这张图可以看得很清楚: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 权限,也可能无法在所有场景下正常显示。建议密切关注官方文档更新。

六、实际项目中的选择策略

我个人习惯这样选择:

  1. 新项目(minSdk >= 26):只用 TYPE_APPLICATION_OVERLAY。省心、合规、未来兼容性好。
  2. 老项目适配:根据 API 版本做分支判断。API 26+ 用新的,以下用旧的。
  3. 特殊场景(如锁屏控制):评估是否真的需要。如果必须,做好被拒的心理准备,或者走企业分发渠道。

你想想看,Google 为什么要推 TYPE_APPLICATION_OVERLAY?说白了就是为了安全。老版本的 TYPE_SYSTEM_ALERT 权限太大,容易被滥用。作为开发者,我们也要跟上平台的步伐。

最后说一句:悬浮窗功能虽然好用,但不要滥用。用户手机上悬浮窗太多,体验真的很差。我自己的原则是——能不用就不用,用了就要给用户提供关闭选项


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