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 发了两次通知,第二次会覆盖第一次。取消时也会把最新的那条干掉。
NOTIFICATION_ID_DOWNLOAD = 1001。这样取消时一眼就能看出是哪个通知,不会搞混。
11.1.2 cancelAll()
这个简单粗暴——清空当前应用发出的所有通知。
manager.cancelAll();
适合用在「退出登录」、「清除所有」这类场景。但别滥用,用户可能正在看某个通知,你一把全清了,体验不好。
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"。
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() 来移除。
11.4.3 通知的「保留」策略
有些通知,即使应用被杀了,系统也会保留。比如:
- 前台服务的通知
- 系统级的通知(如通话、闹钟)
- 高优先级的通知(priority 为 HIGH 或 MAX)
但普通通知,应用进程被杀后,通知虽然还在,但你再想更新或取消它,就得重新启动进程。所以我的建议是:重要通知用前台服务,普通通知随缘。
11.5 知识体系总览
下面这张图,帮你理清通知管理的核心脉络:
11.6 小结
通知管理,说白了就是三件事:什么时候发,什么时候取消,怎么分组。
- cancel() / cancelAll():手动控制,适合退出、清除等场景。
- setAutoCancel():点击即消,适合消息类通知。
- Tag 管理:用字符串分组,适合复杂业务。
- 保留与清除:持续通知用 setOngoing(),普通通知随系统。
我个人习惯是:能用 Tag 就别只用 ID。Tag 让代码可读性更高,也方便后期维护。你想想看,半年后回来看代码,看到 cancel("download", 2001),一眼就知道是下载任务的通知,比 cancel(2001) 强太多了。