19、通知(Notification):创建通知渠道、构建通知、通知点击跳转、进度条通知
通知,说白了就是App主动找用户说话的方式。我见过太多开发者把通知当成「发个弹窗完事」,结果要么被系统静默拦截,要么用户根本找不到入口。今天咱们就把通知这件事彻底捋清楚。
19.1 通知渠道——Android 8.0的「分水岭」
Android 8.0(API 26)之后,所有通知必须绑定渠道。这不是可选项,是强制要求。我当年第一次适配8.0时,就因为没建渠道,通知死活不显示,排查了半天才发现是渠道没注册。
核心原则:每个渠道代表一类通知,用户可以在系统设置里单独控制每个渠道的开关、声音、震动等行为。
创建渠道的代码其实很简单,但有几个坑要注意:
// 创建通知渠道
private void createNotificationChannel() {
// 渠道ID:唯一标识,后续发通知要用
String channelId = "download_channel";
// 渠道名称:用户可见,建议用中文
CharSequence channelName = "下载进度";
// 渠道描述:用户点开渠道详情时看到
String channelDesc = "显示文件下载的实时进度";
// 重要性级别:直接影响通知能否弹出
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
channel.setDescription(channelDesc);
// 关闭角标(可选)
channel.setShowBadge(false);
// 设置震动模式(可选)
channel.enableVibration(true);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
我曾经踩过的坑:渠道一旦创建,importance级别就不能再改了。如果你在开发阶段设了IMPORTANCE_MIN,上线后想改成IMPORTANCE_HIGH,用户必须卸载重装才能生效。所以上线前一定要想清楚每个渠道的定位。
关于重要性级别,我建议这样选:
| 级别 | 常量 | 表现 | 适用场景 |
|---|---|---|---|
| 紧急 | IMPORTANCE_HIGH | 弹出横幅、有声音 | 来电、闹钟、支付结果 |
| 高 | IMPORTANCE_DEFAULT | 有声音、无横幅 | 新消息、好友请求 |
| 中 | IMPORTANCE_LOW | 无声音、状态栏显示 | 下载进度、后台任务 |
| 低 | IMPORTANCE_MIN | 不弹出、仅通知栏可见 | 同步完成、系统提示 |
19.2 构建通知——从「能发」到「好用」
渠道建好了,接下来就是发通知。我个人习惯把通知构建封装成一个工具类,这样项目中所有通知都走统一入口,方便管理。
// 构建一个基础通知
private void showBasicNotification() {
String channelId = "download_channel";
int notificationId = 1001;
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_download) // 小图标(必填)
.setContentTitle("文件下载中") // 标题
.setContentText("正在下载《Android实战》第19章.pdf") // 内容
.setPriority(NotificationCompat.PRIORITY_LOW) // 兼容低版本
.setAutoCancel(true); // 点击后自动移除
NotificationManager manager = getSystemService(NotificationManager.class);
manager.notify(notificationId, builder.build());
}
小技巧:setSmallIcon一定要用纯透明背景的矢量图,不要用带颜色的图片。否则在部分系统上会显示成一个白色方块,非常难看。我一般用Vector Drawable,尺寸控制在24dp以内。
19.3 通知点击跳转——别让用户「看了白看」
通知最核心的价值是「引导用户回到正确的位置」。如果用户点击通知只是打开App首页,那这个通知基本等于白发了。
正确的做法是用PendingIntent包装一个Intent,指定要跳转的目标页面:
// 创建点击跳转的Intent
Intent intent = new Intent(this, DownloadDetailActivity.class);
intent.putExtra("file_id", "file_12345");
// 注意:FLAG_UPDATE_CURRENT 保证多次点击时数据更新
PendingIntent pendingIntent = PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
// 将PendingIntent设置到通知上
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_download)
.setContentTitle("下载完成")
.setContentText("《Android实战》第19章.pdf 已保存")
.setContentIntent(pendingIntent) // 绑定点击事件
.setAutoCancel(true);
注意:Android 12(API 31)之后,PendingIntent必须显式声明可变性。要么加FLAG_IMMUTABLE(推荐),要么加FLAG_MUTABLE。不加的话,targetSdkVersion 31以上会直接崩溃。这个坑我帮好几个同事排查过。
19.4 进度条通知——让用户「心里有数」
进度条通知是下载类App的标配。但很多人不知道,进度条通知有两种模式:
- 确定进度:你知道总大小,可以算百分比
- 不确定进度:你不知道总大小,只能转圈
先看确定进度的写法:
// 显示进度条通知
private void showProgressNotification(int progress, int max) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_download)
.setContentTitle("下载中")
.setContentText(progress + "%")
.setProgress(max, progress, false) // false表示确定进度
.setOngoing(true); // 不可滑动移除
NotificationManager manager = getSystemService(NotificationManager.class);
manager.notify(NOTIFICATION_ID, builder.build());
}
// 在下载循环中更新进度
for (int i = 0; i <= 100; i += 10) {
showProgressNotification(i, 100);
Thread.sleep(500); // 模拟下载
}
// 下载完成后,移除进度条,改为完成通知
private void onDownloadComplete() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_download)
.setContentTitle("下载完成")
.setContentText("文件已保存到本地")
.setProgress(0, 0, false) // 移除进度条
.setOngoing(false);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.notify(NOTIFICATION_ID, builder.build());
}
不确定进度更简单,把setProgress的第三个参数设为true就行:
// 不确定进度:转圈动画
builder.setProgress(0, 0, true); // true表示不确定进度
我的经验:进度条通知一定要用同一个notificationId去更新,不要每次新建一个ID。否则通知栏里会堆满通知,用户会疯掉的。另外,下载完成后记得把进度条去掉,改成普通通知,这样更清爽。
19.5 知识体系总览
下面这张图把通知的完整流程串起来了,我建议你保存下来,写代码时对照着看:
19.6 避坑指南——我踩过的那些雷
最后分享几个实战中容易翻车的地方:
- 渠道ID重复创建:每次启动App都调用createNotificationChannel()没关系,系统会忽略重复创建。但如果你改了渠道的importance,系统不会更新。所以渠道配置最好写在Application的onCreate里,只执行一次。
- 通知ID冲突:不同模块用同一个notificationId,会导致后发的通知覆盖前面的。我习惯用「模块前缀+自增序号」来生成ID,比如DOWNLOAD_1001、MESSAGE_2001。
- PendingIntent的requestCode:如果两个通知用了相同的requestCode和Intent,系统会复用同一个PendingIntent。想要每个通知独立,requestCode必须不同。
- Android 13的运行时权限:Android 13(API 33)新增了POST_NOTIFICATIONS权限。如果你的targetSdkVersion >= 33,必须在运行时申请这个权限,否则通知发不出去。
总结一下:通知这件事,技术门槛不高,但细节特别多。渠道、跳转、进度、权限,每个环节都有坑。我的建议是:先画好流程图,再动手写代码。把通知当成一个「小产品」来设计,而不是一个「小功能」来凑合。
公众号:蓝海资料掘金营,微信deep3321