9、通知声音与自定义:默认声音、自定义铃声、设置通知的URI、静默通知的实现

通知的声音,说白了就是App跟用户打招呼的方式。你想想看,用户手机锁屏放桌上,一条消息过来,叮咚一声——这声音选得好,用户觉得贴心;选得烂,用户直接关通知权限。我在项目中见过太多因为通知声音太吵被卸载的案例了。

这一章,我们就来聊聊通知声音的那些事。从默认声音到自定义铃声,从URI设置到静默通知,我会把我在实际开发中踩过的坑、总结的经验都抖出来。

9.1 默认声音:系统帮你选好了

大多数情况下,我们直接用系统默认通知声音就够了。怎么用?很简单,创建NotificationChannel时指定一下即可。

// Android 8.0+ 必须走Channel
NotificationChannel channel = new NotificationChannel(
    CHANNEL_ID,
    "消息通知",
    NotificationManager.IMPORTANCE_HIGH
);
// 使用系统默认通知声音
channel.setSound(
    Settings.System.DEFAULT_NOTIFICATION_URI,
    Notification.AUDIO_ATTRIBUTES_DEFAULT
);

这里有个细节:IMPORTANCE_HIGH 才会发出声音。如果你设成 IMPORTANCE_LOW 或更低,系统默认就不响铃了。嗯,这个坑我刚开始也踩过——调了半天代码没声音,最后发现是重要性等级设低了。

💡 我的习惯: 如果通知需要用户立即关注,用 HIGH;如果只是信息提示,用 DEFAULT 就够了。别什么通知都 HIGH,用户会烦的。

9.2 自定义铃声:让通知更有辨识度

有些场景下,我们希望通知声音与众不同。比如聊天App,希望自己的消息提示音跟系统短信区分开。这时候就需要自定义铃声了。

自定义铃声有两种常见方式:

  1. 使用raw资源文件——直接把音频文件放到 res/raw 目录下
  2. 使用文件路径URI——从存储中读取音频文件

先看第一种,最简单:

// 从raw资源获取URI
Uri soundUri = Uri.parse(
    "android.resource://" + getPackageName() + "/" + R.raw.custom_notification
);

NotificationChannel channel = new NotificationChannel(
    CHANNEL_ID,
    "自定义声音",
    NotificationManager.IMPORTANCE_HIGH
);
channel.setSound(soundUri, Notification.AUDIO_ATTRIBUTES_DEFAULT);

注意了,raw目录下的音频文件命名要小写,不能有特殊字符。我有一回放了个 My_Sound.mp3,编译直接报错——Android资源文件命名规则就是这么死板。

⚠️ 避坑指南: 我曾经在项目里用了一个5MB的MP3文件做通知音,结果用户反馈通知延迟严重。后来查了官方文档才发现,通知音频最好控制在100KB以内,格式推荐OGG或M4A。太大文件会影响系统处理速度。

9.3 设置通知的URI:更灵活的声音方案

有时候,我们希望用户能自己选择通知声音。比如在设置页面提供一个铃声选择器。这时候就需要用到URI了。

Android系统提供了一个非常好用的 RingtoneManager,可以调起系统铃声选择界面:

// 调起系统铃声选择器
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "选择通知声音");
// 设置默认选中项
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentUri);
startActivityForResult(intent, REQUEST_CODE_SOUND);

用户选择后,我们在 onActivityResult 中拿到URI:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_CODE_SOUND && resultCode == RESULT_OK) {
        Uri pickedUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
        if (pickedUri != null) {
            // 保存URI到SharedPreferences
            saveSoundUri(pickedUri);
            // 更新Channel
            updateChannelSound(pickedUri);
        }
    }
}

拿到URI后,怎么应用到Channel?很简单:

private void updateChannelSound(Uri soundUri) {
    NotificationChannel channel = notificationManager.getNotificationChannel(CHANNEL_ID);
    if (channel != null) {
        // 删除旧Channel再重建
        notificationManager.deleteNotificationChannel(CHANNEL_ID);
        
        NotificationChannel newChannel = new NotificationChannel(
            CHANNEL_ID,
            "消息通知",
            NotificationManager.IMPORTANCE_HIGH
        );
        newChannel.setSound(soundUri, Notification.AUDIO_ATTRIBUTES_DEFAULT);
        notificationManager.createNotificationChannel(newChannel);
    }
}

核心要点: Channel一旦创建,声音属性就不能直接修改。你必须先删除再重建。删除Channel会导致该Channel下所有未清除的通知被移除——所以最好在用户没有未读通知时操作。

9.4 静默通知的实现:不打扰也是一种温柔

有些通知不需要发出声音。比如后台同步完成、位置更新、或者一些不太重要的状态变化。这时候,静默通知就是最佳选择。

实现静默通知,说白了就是把声音关掉。有两种方式:

方式一:低重要性Channel

NotificationChannel silentChannel = new NotificationChannel(
    CHANNEL_SILENT,
    "静默通知",
    NotificationManager.IMPORTANCE_LOW
);
// 不设置声音,默认就是静默
silentChannel.setSound(null, null);
// 或者直接禁用声音
silentChannel.enableVibration(false);

IMPORTANCE_LOW 级别的通知,系统默认不会发出声音,也不会在状态栏弹出横幅。它只会悄悄出现在通知栏里。我个人习惯把这种Channel用在「日志类」通知上。

方式二:在通知构建时控制

如果你不想单独建一个Channel,也可以在发送通知时通过 setSilent 方法控制:

// 注意:这个方法只在Android 7.0+有效
Notification notification = new Notification.Builder(context, CHANNEL_ID)
    .setContentTitle("静默更新")
    .setContentText("后台数据已同步")
    .setSmallIcon(R.drawable.ic_sync)
    .setSilent(true)  // 强制静默
    .build();

不过说实话,我更推荐用Channel级别来控制。为什么呢?因为 setSilent(true) 只是临时静默一次,而Channel级别的设置是持久化的。用户如果手动改了Channel设置,你的 setSilent 可能就不生效了。

💡 我的经验: 在项目中,我一般设计三个Channel:
- 紧急通知(HIGH + 声音)
- 普通通知(DEFAULT + 声音)
- 静默通知(LOW + 无声音)
这样用户可以在系统设置里自由调整,体验更好。

9.5 知识体系一览

说了这么多,我们来画个图,把通知声音的整个体系理清楚:

通知声音体系 通知声音 默认声音 Settings.System.DEFAULT_NOTIFICATION_URI 自定义铃声 res/raw 或 文件URI 静默通知 IMPORTANCE_LOW + null声音 实现方式对比 • 默认声音:最简单,系统自动处理,适合大多数场景 • 自定义铃声:需要提供音频资源,注意文件大小和格式 • 静默通知:通过Channel级别控制,或 setSilent(true) 临时静默

9.6 几个容易忽略的细节

最后,我再补充几个实际开发中容易踩的坑:

场景 常见问题 解决方案
Channel已存在 修改声音属性不生效 先删除Channel再重建
音频文件过大 通知延迟或卡顿 控制在100KB以内,用OGG格式
URI权限问题 自定义URI无法播放 确保有 READ_EXTERNAL_STORAGE 权限
静默通知仍震动 用户反馈有震动 显式调用 enableVibration(false)
⚠️ 重要提醒: 我曾经遇到过一个线上问题——用户反馈通知没声音,排查了半天发现是手机开启了「勿扰模式」。所以,如果你的通知声音不工作,先检查系统设置,别一上来就怀疑代码。

通知声音这件事,说大不大,说小不小。但用户体验往往就体现在这些细节里。选对了声音,用户觉得你懂他;选错了,用户直接把你关进小黑屋。嗯,希望这一章的内容能帮你少走些弯路。


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