17、通知的适配与兼容:不同Android版本的行为差异、Support Library与AndroidX、NotificationCompat的兼容处理
做Android通知开发,最头疼的是什么?
不是通知本身有多复杂,而是——版本碎片化。
我入行那会儿,Android 4.4 还是主流。那时候写通知,一个 Notification.Builder 搞定所有。后来 Android 5.0 引入了锁屏通知,Android 6.0 加了 Doze 模式,Android 7.0 有了通知渠道的雏形,Android 8.0 直接推翻了之前的通知模型……
你想想看,如果你的App要兼容 Android 4.4 到 Android 14,光通知这块,就得处理至少五六种行为差异。
嗯,这一章我们就来聊聊,怎么优雅地搞定这些兼容问题。
17.1 不同版本的行为差异:一张表说清楚
先别急着写代码。我习惯先把「敌人」摸清楚。下面这张表,是我这些年踩坑总结的——每个大版本对通知的关键改动:
| Android 版本 | API Level | 关键变化 |
|---|---|---|
| 4.4 (KitKat) | 19 | 引入 Notification.Builder,但功能有限 |
| 5.0 (Lollipop) | 21 | 锁屏通知、Heads-up 横幅通知 |
| 6.0 (Marshmallow) | 23 | Doze 模式影响通知延迟,Runtime Permission 影响通知权限 |
| 7.0 (Nougat) | 24 | 通知分组(NotificationGroup)、直接回复(RemoteInput) |
| 8.0 (Oreo) | 26 | 强制通知渠道(NotificationChannel),所有通知必须绑定渠道 |
| 9.0 (Pie) | 28 | 通知渠道分组、Bubbles 气泡(实验性) |
| 10+ | 29+ | 通知权限开关、POST_NOTIFICATIONS 运行时权限(Android 13) |
看到没?从 Android 8.0 开始,通知的玩法彻底变了。如果你还在用老一套的 new NotificationCompat.Builder(context) 不传渠道ID……嗯,在 Android 8.0 以上直接崩溃。
17.2 Support Library 与 AndroidX:历史的抉择
说到兼容,就绕不开 Support Library 和 AndroidX。
简单回顾一下:
- Support Library:Google 早期提供的兼容包,比如
com.android.support:appcompat-v7。版本号到 28.0.0 就停了。 - AndroidX:2018 年 Google I/O 推出的替代品,包名改为
androidx.*。比如androidx.core:core。
说白了,AndroidX 就是 Support Library 的「升级版 + 重构版」。如果你现在还在用 Support Library,我建议你尽快迁移。因为 Google 已经停止更新 Support Library 了,新特性、新 API 只会在 AndroidX 里出现。
迁移其实不复杂。在 gradle.properties 里加上这两行:
android.useAndroidX=true
android.enableJetifier=true
然后,把 com.android.support 的依赖全部替换成 androidx 对应的包。比如:
// 旧
implementation 'com.android.support:appcompat-v7:28.0.0'
// 新
implementation 'androidx.appcompat:appcompat:1.6.1'
至于通知相关的兼容,核心依赖是这个:
implementation 'androidx.core:core:1.12.0'
这个包里包含了 NotificationCompat 的所有兼容逻辑。
17.3 NotificationCompat:你的兼容救星
NotificationCompat 是什么?
它是 AndroidX 提供的一个「万能适配器」。你写一份代码,它在不同 Android 版本上自动选择正确的实现方式。
举个例子。在 Android 8.0 以下,通知不需要渠道。在 Android 8.0 以上,必须传渠道ID。用 NotificationCompat 怎么写?
// 创建通知渠道(兼容写法)
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
"default_channel",
"默认通知",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
}
// 发送通知(兼容写法)
private void sendNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "default_channel")
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("标题")
.setContentText("内容")
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
NotificationManagerCompat manager = NotificationManagerCompat.from(this);
manager.notify(1001, builder.build());
}
注意看:
NotificationCompat.Builder自动处理了渠道ID的兼容——在 Android 8.0 以下,它忽略渠道参数;在 8.0 以上,它使用你传入的渠道ID。NotificationManagerCompat是NotificationManager的兼容版,内部处理了权限检查、Doze 模式等差异。
NotificationCompat 和 NotificationManagerCompat。哪怕你的 minSdk 已经到 26(Android 8.0),我也建议用兼容库。为什么?因为万一哪天你要降 minSdk,或者要处理厂商 ROM 的奇葩行为,兼容库能帮你兜底。
17.4 核心兼容点:逐个击破
下面这几个场景,是我在项目中反复遇到的。每个都值得你留意。
17.4.1 通知渠道的兼容
Android 8.0 强制要求渠道。但你的 App 可能还在支持 Android 7.0 以下的设备。
解决方案:
- 在
Application.onCreate()或MainActivity.onCreate()中,判断 API 版本,创建渠道。 - 渠道只需要创建一次,重复创建不会报错。
- 渠道的
importance决定了通知的打扰级别。别把「不重要」的通知设成IMPORTANCE_HIGH,否则用户会骂你。
17.4.2 通知权限的兼容(Android 13+)
Android 13(API 33)引入了 POST_NOTIFICATIONS 运行时权限。这意味着,你的 App 必须像申请相机、位置一样,弹窗请求用户允许发送通知。
兼容写法:
// 检查权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
// 请求权限
requestPermissions(
new String[]{Manifest.permission.POST_NOTIFICATIONS},
REQUEST_CODE_POST_NOTIFICATIONS
);
return;
}
}
// 权限已获取,发送通知
sendNotification();
注意:在 Android 13 以下,不需要这个权限。所以一定要加 Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU 判断。
checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)。结果编译没问题,但运行时抛了 NoSuchFieldError。因为 Android 12 的 SDK 里根本没有这个常量。从那以后,我所有权限相关代码,都严格包裹在版本判断里。
17.4.3 锁屏通知与隐私
Android 5.0 开始支持锁屏通知。但不同版本对锁屏通知的控制方式不同:
- Android 5.0-7.1:通过
setVisibility()控制。 - Android 8.0+:通过渠道的
setLockscreenVisibility()控制。
兼容写法:
// 渠道级别(Android 8.0+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
channel.setLockscreenVisibility(NotificationCompat.VISIBILITY_PRIVATE);
}
// 通知级别(所有版本)
builder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
建议两个都设。渠道级别是「默认值」,通知级别可以覆盖。
17.4.4 通知分组与直接回复
Android 7.0 引入了通知分组和 RemoteInput。如果你的 App 要兼容 Android 6.0 以下,需要做降级处理。
我的做法是:
- 用
NotificationCompat的setGroup()和addAction()方法。 - 在 Android 7.0 以下,分组效果会丢失,但通知本身还能正常显示。
- 直接回复(
RemoteInput)在 Android 7.0 以下不支持,需要自己实现一个PendingIntent跳转到 Activity 让用户输入。
17.5 知识体系:一张图看懂通知兼容
说了这么多,我画了一张图,帮你把整个兼容体系串起来:
17.6 实战建议:我的兼容清单
最后,分享一份我自己的「通知兼容检查清单」。每次写通知代码,我都会过一遍:
- 渠道创建:是否在
Application或Activity中创建了渠道?渠道ID是否与NotificationCompat.Builder一致? - 权限处理:Android 13+ 是否处理了
POST_NOTIFICATIONS权限?权限被拒绝时是否有降级方案? - 锁屏可见性:是否设置了
setVisibility()?敏感内容是否用了VISIBILITY_PRIVATE? - 优先级:是否设置了
setPriority()?渠道的importance是否与通知优先级匹配? - 兼容库:是否全部使用
NotificationCompat和NotificationManagerCompat? - 测试覆盖:是否在 Android 7.0、8.0、10、13 上分别测试过?
嗯,这一章的内容就到这里。通知兼容说难不难,说简单也不简单。核心就是一句话:用兼容库,做版本判断,别偷懒。
📌 一句话总结: NotificationCompat 是你的盾牌,AndroidX 是你的武器,版本判断是你的战术。三者结合,才能在 Android 的碎片化丛林中游刃有余。