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 或更低,系统默认就不响铃了。嗯,这个坑我刚开始也踩过——调了半天代码没声音,最后发现是重要性等级设低了。
9.2 自定义铃声:让通知更有辨识度
有些场景下,我们希望通知声音与众不同。比如聊天App,希望自己的消息提示音跟系统短信区分开。这时候就需要自定义铃声了。
自定义铃声有两种常见方式:
- 使用raw资源文件——直接把音频文件放到 res/raw 目录下
- 使用文件路径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资源文件命名规则就是这么死板。
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 可能就不生效了。
- 紧急通知(HIGH + 声音)
- 普通通知(DEFAULT + 声音)
- 静默通知(LOW + 无声音)
这样用户可以在系统设置里自由调整,体验更好。
9.5 知识体系一览
说了这么多,我们来画个图,把通知声音的整个体系理清楚:
9.6 几个容易忽略的细节
最后,我再补充几个实际开发中容易踩的坑:
| 场景 | 常见问题 | 解决方案 |
|---|---|---|
| Channel已存在 | 修改声音属性不生效 | 先删除Channel再重建 |
| 音频文件过大 | 通知延迟或卡顿 | 控制在100KB以内,用OGG格式 |
| URI权限问题 | 自定义URI无法播放 | 确保有 READ_EXTERNAL_STORAGE 权限 |
| 静默通知仍震动 | 用户反馈有震动 | 显式调用 enableVibration(false) |
通知声音这件事,说大不大,说小不小。但用户体验往往就体现在这些细节里。选对了声音,用户觉得你懂他;选错了,用户直接把你关进小黑屋。嗯,希望这一章的内容能帮你少走些弯路。
公众号:蓝海资料掘金营,微信deep3321