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 以上直接崩溃。

⚠️ 我曾经踩过的坑: 有一次上线前测试,发现 Android 8.0 设备上通知死活不显示。查了半天,原来是忘了创建 NotificationChannel。从那以后,我养成了一个习惯——所有通知代码,第一行先检查 API 版本

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。
  • NotificationManagerCompatNotificationManager 的兼容版,内部处理了权限检查、Doze 模式等差异。
💡 我个人习惯: 所有通知相关代码,一律使用 NotificationCompatNotificationManagerCompat。哪怕你的 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 判断。

⚠️ 我曾经踩过的坑: 有一次在 Android 12 设备上测试,忘了加版本判断,直接调用了 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 以下,需要做降级处理。

我的做法是:

  • NotificationCompatsetGroup()addAction() 方法。
  • 在 Android 7.0 以下,分组效果会丢失,但通知本身还能正常显示。
  • 直接回复(RemoteInput)在 Android 7.0 以下不支持,需要自己实现一个 PendingIntent 跳转到 Activity 让用户输入。

17.5 知识体系:一张图看懂通知兼容

说了这么多,我画了一张图,帮你把整个兼容体系串起来:

Android 通知兼容知识体系 NotificationCompat 版本差异处理 API 版本判断 渠道创建(8.0+) 权限请求(13+) AndroidX 兼容库 NotificationManagerCompat NotificationCompat.Builder RemoteInput 兼容 行为差异适配 锁屏通知(5.0+) 通知分组(7.0+) Doze 模式(6.0+) 核心原则:一份代码,兼容所有版本 使用 NotificationCompat + 版本判断 + AndroidX

17.6 实战建议:我的兼容清单

最后,分享一份我自己的「通知兼容检查清单」。每次写通知代码,我都会过一遍:

  1. 渠道创建:是否在 ApplicationActivity 中创建了渠道?渠道ID是否与 NotificationCompat.Builder 一致?
  2. 权限处理:Android 13+ 是否处理了 POST_NOTIFICATIONS 权限?权限被拒绝时是否有降级方案?
  3. 锁屏可见性:是否设置了 setVisibility()?敏感内容是否用了 VISIBILITY_PRIVATE
  4. 优先级:是否设置了 setPriority()?渠道的 importance 是否与通知优先级匹配?
  5. 兼容库:是否全部使用 NotificationCompatNotificationManagerCompat
  6. 测试覆盖:是否在 Android 7.0、8.0、10、13 上分别测试过?

嗯,这一章的内容就到这里。通知兼容说难不难,说简单也不简单。核心就是一句话:用兼容库,做版本判断,别偷懒

📌 一句话总结: NotificationCompat 是你的盾牌,AndroidX 是你的武器,版本判断是你的战术。三者结合,才能在 Android 的碎片化丛林中游刃有余。

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