26、Notifications:通知渠道、通知样式、Android 13通知权限

通知,说白了就是App跟用户打招呼的方式。但Android的通知系统这些年改了不少,尤其是Android 8.0引入通知渠道,Android 13又搞了个运行时权限。我刚开始接手一个老项目时,发现通知发不出去,查了半天——原来是没适配通知渠道。嗯,这坑我踩过。

通知渠道(Notification Channel)

Android 8.0(API 26)之后,所有通知都必须绑定一个渠道。渠道就像分类标签,用户可以在系统设置里单独控制每个渠道的开关、声音、震动等。我个人习惯把通知分成三类:

  • 重要消息:比如订单支付成功、紧急提醒。用高优先级,带声音震动。
  • 普通消息:比如活动推送、内容更新。用默认优先级,声音可选。
  • 服务消息:比如后台下载进度、音乐播放。用最低优先级,甚至静音。

创建渠道的代码很简单,但要注意——渠道一旦创建,除了名称和描述,其他属性基本改不了。我遇到过产品经理要求改渠道声音,结果发现只能删掉App重新安装才能生效。所以,渠道设计要提前想清楚。

// 创建通知渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    val channelId = "order_channel"
    val channelName = "订单通知"
    val importance = NotificationManager.IMPORTANCE_HIGH
    val channel = NotificationChannel(channelId, channelName, importance).apply {
        description = "接收订单相关的推送消息"
        enableVibration(true)
        setSound(
            RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
            Notification.AUDIO_ATTRIBUTES_DEFAULT
        )
    }
    val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    notificationManager.createNotificationChannel(channel)
}
小技巧:渠道ID最好用常量定义,别写死字符串。我习惯在companion object里统一管理,方便后续维护。

通知样式:让通知更好看

Android提供了几种内置样式,不用自己画布局。常用的有:

样式 适用场景 说明
BigTextStyle 长文本消息 展开后显示完整内容,适合新闻、邮件
InboxStyle 多条消息汇总 像聊天软件那样列出多条记录
BigPictureStyle 图片推广 展开后显示大图,适合营销活动
MessagingStyle 即时通讯 支持会话模式,显示头像和时间戳

举个例子,我用MessagingStyle做过一个客服聊天通知,效果比普通通知好很多。用户可以直接在通知栏看到对话上下文,点击还能快速回复。

// 使用 MessagingStyle
val person = Person.Builder()
    .setName("客服小张")
    .setIcon(Icon.createWithResource(this, R.drawable.ic_support))
    .build()

val message1 = Notification.MessagingStyle.Message(
    "您好,请问有什么可以帮您?",
    System.currentTimeMillis() - 60000,
    person
)

val message2 = Notification.MessagingStyle.Message(
    "我想查询一下订单状态",
    System.currentTimeMillis(),
    null // 用户自己发的消息,不设person
)

val notification = Notification.Builder(this, channelId)
    .setSmallIcon(R.drawable.ic_notification)
    .setStyle(Notification.MessagingStyle(person)
        .addMessage(message1)
        .addMessage(message2))
    .build()
注意:MessagingStyle要求API 28以上。如果App要兼容低版本,记得做版本判断。我曾经因为没加判断,在Android 7的设备上直接崩溃了。

Android 13 通知权限

Android 13(API 33)把通知权限变成了运行时权限。也就是说,App必须像请求相机、定位那样,弹窗让用户同意才能发通知。这个改动影响很大——用户如果拒绝,你的通知就全废了。

权限声明很简单,在AndroidManifest里加上:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

然后在代码里请求权限:

// 检查并请求通知权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) 
        != PackageManager.PERMISSION_GRANTED) {
        requestPermissions(
            arrayOf(Manifest.permission.POST_NOTIFICATIONS),
            NOTIFICATION_PERMISSION_REQUEST_CODE
        )
    }
}

这里有个坑:用户拒绝后,系统会弹出一个「不再询问」的选项。如果用户勾选了,你就再也弹不了权限请求了。我建议在用户拒绝后,引导他去系统设置里手动打开。代码可以这样写:

// 用户拒绝后,引导去设置
override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<String>,
    grantResults: IntArray
) {
    if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
        if (grantResults.isNotEmpty() && 
            grantResults[0] != PackageManager.PERMISSION_GRANTED) {
            // 用户拒绝了,弹个Dialog引导去设置
            showSettingsDialog()
        }
    }
}

private fun showSettingsDialog() {
    AlertDialog.Builder(this)
        .setTitle("需要通知权限")
        .setMessage("为了及时收到消息,请在设置中开启通知权限")
        .setPositiveButton("去设置") { _, _ ->
            val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
                data = Uri.parse("package:$packageName")
            }
            startActivity(intent)
        }
        .setNegativeButton("取消", null)
        .show()
}
核心要点:Android 13的通知权限是可选权限。用户拒绝后,App不能崩溃,也不能反复弹窗骚扰用户。优雅处理拒绝,才是专业做法。

知识体系总览

下面这张图梳理了本章的核心逻辑,从渠道创建到样式选择,再到权限处理,一条线串起来:

Notifications 知识体系 通知渠道 通知样式 通知权限 创建渠道(API 26+) 渠道分组管理 重要性等级设置 BigTextStyle InboxStyle BigPictureStyle MessagingStyle 运行时权限(API 33+) 权限声明 拒绝后引导设置 三者配合:渠道分类 → 样式展示 → 权限保障

你看,通知这件事,从渠道到样式再到权限,环环相扣。渠道是基础,样式是体验,权限是门槛。少了哪一环,通知都可能出问题。

我的建议:新项目一开始就把通知渠道设计好,别等上线了再改。权限请求的时机也很重要——别一启动就弹,等用户真正需要通知时再请求,成功率会高很多。

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