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 知识体系总览

下面这张图把通知的完整流程串起来了,我建议你保存下来,写代码时对照着看:

通知(Notification) 完整流程 1. 创建通知渠道 createNotificationChannel() 2. 构建通知对象 NotificationCompat.Builder 3. 设置通知属性 标题/内容/图标/优先级 基础通知 setContentTitle() setContentText() 点击跳转 setContentIntent() PendingIntent 进度条通知 setProgress() 确定/不确定进度 4. 发送通知 manager.notify(id, builder) 注意:Android 8.0+ 必须创建渠道,否则通知不显示

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