11、通知取消与管理:cancel()与cancelAll()、通知的自动取消、通过Tag管理通知、通知的保留与清除

通知发出去只是第一步,怎么管好它们才是真功夫。我见过不少项目,通知发得满天飞,用户一划拉,通知栏乱成一锅粥。说白了,通知管理就是三个字:收放自如

这一章,咱们就聊聊通知的取消、自动消失、Tag 分组,以及系统是怎么决定「留」还是「删」的。

11.1 手动取消:cancel() 与 cancelAll()

最直接的取消方式,就是调用 NotificationManager 的两个方法。

11.1.1 cancel(int id)

取消指定 ID 的通知。这个 ID 就是你发通知时传的那个。

// 发送通知
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.notify(1001, notification);

// 取消通知
manager.cancel(1001);

嗯,这里要注意:ID 必须是唯一的。如果你用同一个 ID 发了两次通知,第二次会覆盖第一次。取消时也会把最新的那条干掉。

我的习惯:我会把通知 ID 定义成常量,放在一个单独的类里。比如 NOTIFICATION_ID_DOWNLOAD = 1001。这样取消时一眼就能看出是哪个通知,不会搞混。

11.1.2 cancelAll()

这个简单粗暴——清空当前应用发出的所有通知。

manager.cancelAll();

适合用在「退出登录」、「清除所有」这类场景。但别滥用,用户可能正在看某个通知,你一把全清了,体验不好。

注意:cancelAll() 只会取消你当前应用的通知,不会动其他应用的。另外,如果通知已经被用户手动划掉了,再调 cancel() 也不会报错,只是没效果而已。

11.2 自动取消:setAutoCancel()

有些通知,用户点一下就应该消失。比如「新消息提醒」,点进去看了,通知还挂在那,多尴尬。

这时候就用 setAutoCancel(true)

Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
        .setContentTitle("新消息")
        .setContentText("你有一条未读消息")
        .setSmallIcon(R.drawable.ic_message)
        .setAutoCancel(true)  // 点击后自动取消
        .setContentIntent(pendingIntent)
        .build();

manager.notify(1002, notification);

用户点击通知后,系统会自动帮你调用 cancel()。省心。

但有个坑:如果你没有设置 PendingIntent,setAutoCancel 是不生效的。 因为系统不知道「点击后该干嘛」,自然也就没法触发取消逻辑。我曾经在这个问题上 debug 了半天,最后发现是忘了设 intent。

11.3 通过 Tag 管理通知

ID 是整数,有时候不够用。比如你要管理同一类通知,但 ID 可能重复。这时候 Tag 就派上用场了。

Tag 是一个字符串,可以跟 ID 组合成一个唯一键。

// 发送带 Tag 的通知
manager.notify("download", 2001, notification);

// 取消带 Tag 的通知
manager.cancel("download", 2001);

Tag 的好处是:你可以用有意义的字符串来分组。比如所有下载相关的通知都用 "download" 这个 Tag,所有聊天通知用 "chat"

实际场景:我在做一个文件下载器时,每个下载任务都有一个唯一的 taskId。我用 taskId 作为 Tag,通知 ID 固定为 3001。这样取消某个下载任务的通知时,直接 cancel(taskId, 3001) 就行,不会误删其他任务的通知。

Tag 还有一个妙用:更新通知。如果你用同一个 Tag + ID 发新通知,系统会认为这是更新,而不是新建。这样通知栏就不会闪一下。

11.4 通知的保留与清除

通知发出去后,什么时候该留,什么时候该清?系统有一套规则,但开发者也可以干预。

11.4.1 系统默认行为

操作 结果
用户手动划掉 通知被移除,触发 onNotificationRemoved()
点击通知(setAutoCancel=true) 通知自动移除
调用了 cancel() / cancelAll() 通知被移除
应用被杀死 通知保留(除非是 foreground service 的通知)
系统内存不足 系统可能移除部分通知,但优先级高的会保留

11.4.2 如何让通知「不可清除」

有些通知,比如正在播放音乐、正在下载文件,你不想让用户划掉。怎么办?

答案是:使用前台服务 + 持续通知

// 在 foreground service 中
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
        .setContentTitle("正在下载")
        .setContentText("进度:45%")
        .setSmallIcon(R.drawable.ic_download)
        .setOngoing(true)  // 设置为持续通知
        .build();

startForeground(3001, notification);

setOngoing(true) 会让通知变成「持续型」。用户不能划掉,只能通过 stopForeground() 或 stopSelf() 来移除。

我曾经踩过的坑:在 Android 12 以上,如果前台服务没有在 5 秒内调用 startForeground(),系统会直接 crash。所以记得在 service 的 onCreate() 或 onStartCommand() 里尽快启动前台通知。

11.4.3 通知的「保留」策略

有些通知,即使应用被杀了,系统也会保留。比如:

  • 前台服务的通知
  • 系统级的通知(如通话、闹钟)
  • 高优先级的通知(priority 为 HIGH 或 MAX)

但普通通知,应用进程被杀后,通知虽然还在,但你再想更新或取消它,就得重新启动进程。所以我的建议是:重要通知用前台服务,普通通知随缘

11.5 知识体系总览

下面这张图,帮你理清通知管理的核心脉络:

通知管理核心 手动取消 cancel(int id) cancelAll() 自动取消 setAutoCancel(true) 需配合 PendingIntent Tag 管理 cancel(tag, id) 通知管理 = 手动取消 + 自动取消 + Tag 分组 + 保留策略

11.6 小结

通知管理,说白了就是三件事:什么时候发,什么时候取消,怎么分组

  • cancel() / cancelAll():手动控制,适合退出、清除等场景。
  • setAutoCancel():点击即消,适合消息类通知。
  • Tag 管理:用字符串分组,适合复杂业务。
  • 保留与清除:持续通知用 setOngoing(),普通通知随系统。

我个人习惯是:能用 Tag 就别只用 ID。Tag 让代码可读性更高,也方便后期维护。你想想看,半年后回来看代码,看到 cancel("download", 2001),一眼就知道是下载任务的通知,比 cancel(2001) 强太多了。

避坑指南:如果你在 Service 里发通知,别忘了在 onDestroy() 里取消。否则 Service 销毁了,通知还挂在那,用户点一下可能 crash。我曾经因为这个被测试小姐姐追着骂了一下午。

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