2. 创建第一个 Material 3 通知:渠道创建、通知构建、显示流程与代码模板
2.1 通知渠道原理与作用
Android 8.0(API 26)引入了通知渠道(Notification Channel)机制,所有通知必须归属于一个已注册的渠道。渠道扮演“分类容器”的角色,用户可在系统设置中独立控制每个渠道的开关、声音、振动和弹窗行为。Material 3 在 Android 12+ 上进一步将通知卡片改造为圆角背景+大图标+动态配色,但渠道机制保持不变。
| 属性 | 说明 | 设置后可否修改 |
|---|---|---|
| 渠道 ID | 唯一标识,字符串常量 | 不可改 |
| 渠道名称 | 显示给用户的渠道标签 | 可改 |
| 重要性级别 | HIGH / DEFAULT / LOW / MIN | 不可改(需删除重建) |
| 渠道描述 | 用户点击“齿轮”时看到的说明文字 | 可改 |
⚠ 重要性降级陷阱
渠道一旦创建,重要性级别便固化在系统中。即使用户在设置中关闭该渠道,应用也无法通过代码提升重要性。因此首次创建时务必选择正确的级别。
渠道一旦创建,重要性级别便固化在系统中。即使用户在设置中关闭该渠道,应用也无法通过代码提升重要性。因此首次创建时务必选择正确的级别。
2.2 完整创建与显示流程概览
下图将“渠道注册 → 通知构建 → 权限申请 → 发送 → 展示”五个核心阶段串联为一条流水线。每个阶段都包含对应的 API 调用和版本适配逻辑。
2.3 渠道创建 API 与最佳实践
渠道注册必须在 Application.onCreate() 或首个 Activity 的 onCreate() 中执行,确保系统在收到第一条通知前已注册。渠道 ID 建议使用常量避免拼写错误。
// Kotlin — 渠道创建模板
class App : Application() {
companion object {
const val CHANNEL_GENERAL = "channel_general"
const val CHANNEL_ALARM = "channel_alarm"
}
override fun onCreate() {
super.onCreate()
createNotificationChannels()
}
private fun createNotificationChannels() {
val general = NotificationChannel(
CHANNEL_GENERAL,
"一般通知",
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "包含订单、物流等一般性提醒"
enableVibration(true)
setShowBadge(true)
}
val alarm = NotificationChannel(
CHANNEL_ALARM,
"闹钟提醒",
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "闹钟与计时器到点提醒"
enableVibration(true)
setSound(
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM),
Notification.AUDIO_ATTRIBUTES_DEFAULT
)
}
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(general)
manager.createNotificationChannel(alarm)
}
}
💡 渠道复用检测
同一渠道 ID 重复调用
同一渠道 ID 重复调用
createNotificationChannel() 不会产生副作用,系统会直接忽略。因此你可以在每个 Activity 的 onCreate() 中安全调用,无需额外判重。
2.4 通知构建器与 Material 3 样式
使用 NotificationCompat.Builder 构建通知,核心参数如下表所示。Material 3 在 Android 12+ 上会自动应用圆角卡片、大图标和 Monet 动态色,但开发者仍需提供小图标和标题。
| 方法 | 参数说明 | 必填 |
|---|---|---|
setSmallIcon() | 状态栏小图标(白色单色) | ✔ |
setContentTitle() | 通知标题(单行) | ✔ |
setContentText() | 通知正文(收起状态) | ✔ |
setStyle() | 展开样式:BigText / BigPicture / Inbox / Messaging | 推荐 |
setPriority() | 兼容渠道重要性(API 26- 生效) | 推荐 |
setContentIntent() | 点击跳转 PendingIntent | 推荐 |
setAutoCancel() | 点击后自动移除通知 | 推荐 |
// Kotlin — 构建通知示例
val notification = NotificationCompat.Builder(this, App.CHANNEL_GENERAL)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("订单已发货")
.setContentText("您的订单 #2024001 已发出,预计3天到达。")
.setStyle(
NotificationCompat.BigTextStyle()
.bigText("您的订单 #2024001 已发出,预计3天到达。\n快递单号:SF1234567890\n点击查看物流详情。")
)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent) // 点击跳转
.setAutoCancel(true)
.build()
2.5 运行时权限:POST_NOTIFICATIONS
Android 13(API 33)新增 android.permission.POST_NOTIFICATIONS 运行时权限。低于 33 的设备自动授予;33+ 必须动态申请,且用户可拒绝或关闭。
// Kotlin — 权限检查与请求
private fun requestNotificationPermission() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
if (ContextCompat.checkSelfPermission(
this, Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) return
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
PERMISSION_REQUEST_CODE
)
}
// 在 Activity 中接收结果
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED
) {
// 授权成功,发送通知
sendNotification()
} else {
// 用户拒绝,可引导至系统设置
showPermissionDeniedDialog()
}
}
}
⚠ Android 13 权限行为变更
即使用户授权,后续仍可在系统设置中关闭渠道。应用无法通过代码重新开启,只能引导用户手动操作。务必在
即使用户授权,后续仍可在系统设置中关闭渠道。应用无法通过代码重新开启,只能引导用户手动操作。务必在
sendNotification() 前检查 NotificationManager.areNotificationsEnabled()。
2.6 发送通知与 ID 管理
每条通知需要一个唯一 ID(整型)。覆盖相同 ID 会更新已有通知;使用新 ID 则创建新通知。推荐用 System.currentTimeMillis().toInt() 或自增计数器。
// Kotlin — 发送通知
private fun sendNotification() {
if (!isChannelEnabled()) return // 检查渠道是否被用户关闭
val notificationManager =
getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val notification = buildNotification() // 调用构建器
val notificationId = System.currentTimeMillis().toInt()
notificationManager.notify(notificationId, notification)
}
private fun isChannelEnabled(): Boolean {
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val channel = manager.getNotificationChannel(App.CHANNEL_GENERAL)
return channel != null &&
channel.importance != NotificationManager.IMPORTANCE_NONE
}
2.7 完整代码模板(Activity 集成)
以下模板将渠道创建、权限申请、通知构建与发送合并为一个可运行的 Activity 示例。
// MainActivity.kt — 完整模板
class MainActivity : AppCompatActivity() {
companion object {
private const val PERMISSION_REQUEST_CODE = 1001
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 步骤1:创建渠道(已在 Application 中完成,此处仅校验)
ensureChannelExists()
// 步骤2:请求权限(API 33+)
requestNotificationPermission()
// 步骤3:点击按钮发送通知
findViewById<Button>(R.id.btn_send).setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
ContextCompat.checkSelfPermission(
this, Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
Toast.makeText(this, "请先授予通知权限", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
sendNotification()
}
}
private fun ensureChannelExists() {
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if (manager.getNotificationChannel(App.CHANNEL_GENERAL) == null) {
// 若 Application 未创建,此处兜底创建
val channel = NotificationChannel(
App.CHANNEL_GENERAL,
"一般通知",
NotificationManager.IMPORTANCE_HIGH
)
manager.createNotificationChannel(channel)
}
}
private fun requestNotificationPermission() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
if (ContextCompat.checkSelfPermission(
this, Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) return
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
PERMISSION_REQUEST_CODE
)
}
private fun sendNotification() {
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val intent = Intent(this, DetailActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent = PendingIntent.getActivity(
this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, App.CHANNEL_GENERAL)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("订单已发货")
.setContentText("您的订单 #2024001 已发出")
.setStyle(NotificationCompat.BigTextStyle()
.bigText("您的订单 #2024001 已发出\n快递单号:SF1234567890"))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
val notificationId = System.currentTimeMillis().toInt()
manager.notify(notificationId, notification)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED
) {
Toast.makeText(this, "通知权限已授予", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "请在设置中手动开启通知权限", Toast.LENGTH_LONG).show()
}
}
}
}
2.8 调试与验证方法
- Adb 命令验证渠道:
adb shell dumpsys notification可查看所有已注册渠道及其重要性。 - 设置界面检查:进入 系统设置 → 应用 → 你的应用 → 通知,确认渠道名称和开关状态。
- 日志过滤:使用 Logcat 过滤
NotificationService或NotificationManager,观察系统是否拒绝发送。 - 权限状态确认:
adb shell appops get <包名> POST_NOTIFICATIONS返回allow或deny。
2.9 常见错误与解决方案
| 错误现象 | 根因 | 解决方案 |
|---|---|---|
| 通知不显示,无任何错误日志 | 渠道重要性为 MIN 或 NONE | 创建渠道时使用 IMPORTANCE_HIGH 或 IMPORTANCE_DEFAULT |
| Android 13 上通知不弹 | 未申请 POST_NOTIFICATIONS 权限 | 动态请求权限,并检查用户是否拒绝 |
| 通知显示为“应用图标+空白标题” | setSmallIcon() 使用了彩色图 | 小图标必须为白色透明背景的单色矢量图 |
| 渠道创建后无法提升重要性 | 渠道已固化,系统不允许升级 | 卸载应用或清除数据后重新创建 |
| 点击通知不跳转 | PendingIntent 未设置 FLAG_IMMUTABLE | Android 12+ 必须使用 FLAG_IMMUTABLE |
2.10 性能与稳定性建议
- 渠道创建时机:放在
Application.onCreate()中,避免在每个 Activity 中重复创建。 - 通知 ID 生成:使用
AtomicInteger或System.currentTimeMillis()确保唯一,避免 ID 冲突导致通知被覆盖。 - 权限检查前置:发送前调用
areNotificationsEnabled(),避免因权限关闭导致静默失败。 - 避免高频发送:短时间发送大量通知会触发系统节流(
NotificationManager.INTERRUPTION_FILTER_ALARMS),超过 50 条/秒可能被丢弃。 - 兼容旧版本:API 26 以下设备忽略渠道,但仍需提供
setPriority()控制展示行为。
📌 本章小结
创建 Material 3 通知的标准化流程:注册渠道 → 构建通知 → 请求权限(API 33+) → 发送通知。渠道是通知的“集装箱”,重要性一旦固化不可升级;构建器支持丰富样式,但小图标必须为白色单色;Android 13 起动态权限成为必选项。下一章将深入通知样式定制,包括大图文、进度条和交互按钮。
创建 Material 3 通知的标准化流程:注册渠道 → 构建通知 → 请求权限(API 33+) → 发送通知。渠道是通知的“集装箱”,重要性一旦固化不可升级;构建器支持丰富样式,但小图标必须为白色单色;Android 13 起动态权限成为必选项。下一章将深入通知样式定制,包括大图文、进度条和交互按钮。