如何为不同优先级的通知设置不同的声音、震动和图标?
解读
面试官问的不是“能不能”,而是“怎么做、做得稳、做得合规”。国内 ROM 对通知管控极严,Target SDK 34 以后前台通知必须配渠道,后台通知还要受“省电→无限制”与“通知管理→类别”双重钳制。因此答案必须同时覆盖:
- AOSP 原生机制(渠道、重要性)
- 国内厂商兼容(小米、华为、OPPO、vivo、荣耀、三星)的“私有字段 + 系统权限”差异
- 运行时用户可关闭的风险与降级策略
- 8.0 以下旧机型的 Fallback
知识点
- NotificationChannel:8.0+ 唯一入口,重要性级别 1-4 对应静默、低、默认、高
- 渠道属性:setSound(Uri, AudioAttributes)、setVibrationPattern(long[])、setBypassDnd(true) 需 Manifest 声明 ACCESS_NOTIFICATION_POLICY
- 国内厂商: – 小米:EXTRA_MESSAGE_TYPE、Notification.Builder.setChannelId 后仍要 setSound/CustomView 二次设置,且 MIUI 12+ 强制“悬浮通知”开关 – 华为:HMS 渠道 HwNotificationManager,setImportance 与原生枚举值错位 +1 – OPPO:ColorOS 7 以后禁止自定义震动,只能使用系统内置模式 – vivo:Funtouch 10 以下忽略 setSound,需跳转到“i 管家”引导用户手动开
- 图标:5.0+ 强制单色矢量,透明通道决定着色;国内部分 ROM 会取应用桌面图标缓存,需同时提供 res/mipmap 与 res/drawable-anydpi-v24
- 权限:Android 13 新增 POST_NOTIFICATIONS;小米、华为额外需要“后台弹出界面”权限
- 版本兼容:Support Library 26 以上 NotificationCompat.Builder 内部自动代理渠道;7.x 以下使用 deprecated setPriority 与 setSound/setVibration
- 测试命令:adb shell dumpsys notification 查看 rank、importance、sound|vibrate 字段
答案
- 定义渠道枚举
enum class NotifyPriority {
URGENT, // 高优先级,响铃+长震+悬浮
NORMAL, // 默认优先级,短震+提示音
SILENT // 低优先级,静默
}
- 创建并提交渠道(仅首次)
private fun createChannels(ctx: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val nm = ctx.getSystemService(NotificationManager::class.java)
listOf(
NotificationChannel(
"urgent", "紧急消息",
NotificationManager.IMPORTANCE_HIGH
).apply {
setSound(
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build()
)
vibrationPattern = longArrayOf(0, 300, 200, 300)
enableLights(true)
lightColor = Color.RED
setBypassDnd(true) // 需 Manifest 声明 ACCESS_NOTIFICATION_POLICY
},
NotificationChannel(
"normal", "普通消息",
NotificationManager.IMPORTANCE_DEFAULT
).apply {
vibrationPattern = longArrayOf(0, 200)
setSound(
Uri.parse("android.resource://${ctx.packageName}/raw/notify_normal"),
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.build()
)
},
NotificationChannel("silent", "静默通知",
NotificationManager.IMPORTANCE_LOW)
).forEach { nm.createNotificationChannel(it) }
}
- 国内厂商补丁(以小米为例)
fun buildForMiui(ctx: Context, priority: NotifyPriority): Notification {
val builder = NotificationCompat.Builder(ctx, priority.name.lowercase())
.setSmallIcon(R.drawable.ic_notify_mono)
.setContentTitle("标题")
.setContentText("内容")
when (priority) {
NotifyPriority.URGENT -> {
builder.setDefaults(NotificationCompat.DEFAULT_LIGHTS or NotificationCompat.DEFAULT_SOUND)
.setVibrate(longArrayOf(0, 300, 200, 300))
// MIUI 12+ 需要额外申请“悬浮通知”权限
if (MiuiUtils.isMiui() && !MiuiUtils.canFloat(ctx)) {
MiuiUtils.reqFloat(ctx) // 跳转到权限页
}
}
NotifyPriority.NORMAL -> {
builder.setSound(Uri.parse("android.resource://${ctx.packageName}/raw/notify_normal"))
.setVibrate(longArrayOf(0, 200))
}
else -> { /* silent */ }
}
return builder.build()
}
- 图标适配
res/
drawable-anydpi-v24/
ic_notify_mono.xml // 单色矢量,透明通道
mipmap-xxxhdpi/
ic_launcher.png // 桌面图标,防止部分 ROM 回退取错图
- 权限声明
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
<!-- 国内厂商额外权限 -->
<uses-permission android:name="com.huawei.permission.external_app_settings.USE_COMPONENT"/>
<uses-permission android:name="oppo.permission.OPPO_COMPONENT_SAFE"/>
- 7.x 及以下 Fallback
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
val priority = when(priority){
NotifyPriority.URGENT -> NotificationCompat.PRIORITY_HIGH
NotifyPriority.NORMAL -> NotificationCompat.PRIORITY_DEFAULT
else -> NotificationCompat.PRIORITY_LOW
}
builder.setPriority(priority)
.setSound(uri)
.setVibrate(pattern)
}
- 发送
with(NotificationManagerCompat.from(ctx)) {
if (ActivityCompat.checkSelfPermission(ctx, POST_NOTIFICATIONS) == PERMISSION_GRANTED) {
notify(tag, id, notification)
}
}
拓展思考
- 用户关闭渠道后如何降级?监听 NotificationManagerCompat.getNotificationChannel(channelId).importance == IMPORTANCE_NONE,转用 In-App 横幅或短信补位
- 折叠屏/车载双屏场景:8.0+ 允许为同一渠道再分 Bubble、Conversation,通过 setShortcutId + Person 实现“重要对话”独立声音
- 隐私沙盒与 FCM:未来无设备标识条件下,高优通知需依赖 FCM priority=HIGH 触发 App Standby Bucket 升档,否则渠道再高也可能被限
- 测试自动化:利用 adb shell cmd notification post 带 --es sound/--es vibration 参数模拟不同优先级,结合 uiautomator 检查悬浮、锁屏、横幅是否按预期出现