通知是指Android系统在App应用之外显示的App提供的消息,根据通知的不同类型,用户可以通过点按通知打开App,也可以直接在通知中进行操作。App通过通知可以向用户传递重要的实时信息,增强用户体验,提高用户的参与度。本文将简单介绍如何实现基础通知。
实现基础通知
运行时权限
从Android 13开始,引入了用于App发送通知的新运行时权限POST_NOTIFICATIONS
。在Android 13或更高版本的设备上,当用户授权POST_NOTIFICATIONS
后App才能发送通知。
在AndroidManifest
中声明权限
首先,在AndroidManifest
中声明权限,如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
......
</manifest>
运行时申请权限
除了在AndroidManifest
中声明之外,还需要在运行时申请权限,示例代码如下:
kotlin
class NotificationExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutNotificationExampleActivityBinding
private var checkByNotificationAPI = false
private val notRequestAgainKey = "notRequestAgain"
private val notificationManagerCompat: NotificationManagerCompat by lazy { NotificationManagerCompat.from(this) }
private val postNotificationPermission = Manifest.permission.POST_NOTIFICATIONS
private val singlePermissionRequestLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
// 申请权限回调
// granted = true 表示已授权,granted = false 表示未授权
if (!granted) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, postNotificationPermission)) {
//用户拒绝权限并且系统不再弹出请求权限的弹窗
//保存结果,用于判断下次请求权限时是否使用自定义弹窗
SpUtils.put(notRequestAgainKey, true)
}
}
}
private val intentLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
// 页面关闭回调
// 可以在此对通知是否可用再进行一次判断,但如果不可用最好不要直接再次申请,避免用户厌烦
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutNotificationExampleActivityBinding.inflate(layoutInflater).also {
setContentView(it.root)
}
if (!SpUtils.isInit()) {
SpUtils.init(this)
}
val notRequestAgain = SpUtils.getBoolean(notRequestAgainKey, false)
if (!notificationEnable()) {
if (notRequestAgain) {
// 显示自定义弹窗
showPermissionStatementDialog()
} else {
// 显示系统通知权限弹窗
singlePermissionRequestLauncher.launch(postNotificationPermission)
}
}
}
private fun notificationEnable(): Boolean {
return if (checkByNotificationAPI) {
// 通过通知API判断通知是否可用.
notificationManagerCompat.areNotificationsEnabled()
} else {
// 通过POST_NOTIFICATIONS权限判断通知是否可用.
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || ActivityCompat.checkSelfPermission(this, postNotificationPermission) == PackageManager.PERMISSION_GRANTED
}
}
private fun showPermissionStatementDialog() {
val permissionTipsDialog = AlertDialog.Builder(this)
.setTitle("Statement of Notification Permission")
.setMessage("Receive notifications to improve user experience!")
.setCancelable(true)
.setPositiveButton("grant") { dialog, _ ->
intentLauncher.launch(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { data = Uri.parse("package:$packageName") })
dialog.dismiss()
}
.setNegativeButton("cancel") { dialog, _ ->
dialog.dismiss()
}
.create()
permissionTipsDialog.show()
}
}
PS:通过权限判断或通过通知API判断二者选择其一即可。
效果如图:
创建和管理通知渠道
从 Android 8.0(API 级别 26)开始,通知必须分配到一个通知渠道,否则不会显示该通知。不同的通知渠道可以设置不同的视听行为,且用户可以在设置中对任意通知渠道进行更改,这样可以降低用户直接禁用整个App所有通知的概率。
创建通知渠道
创建通知渠道示例代码如下:
kotlin
class NotificationExampleActivity : AppCompatActivity() {
......
private val notificationManagerCompat: NotificationManagerCompat by lazy { NotificationManagerCompat.from(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val applicationInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(0))
} else {
packageManager.getApplicationInfo(packageName, 0)
}
// 渠道id
val channelId = "system_notification_channel"
// 渠道显示名称
val channelDisplayName = "${getText(applicationInfo.labelRes)} System Notification Channel"
// 渠道的重要性级别
val channelImportance = NotificationManager.IMPORTANCE_DEFAULT
val exampleChannel = NotificationChannel(channelId, channelDisplayName, channelImportance).apply {
// 渠道描述
description = "Receive system-level notifications"
}
notificationManagerCompat.createNotificationChannel(exampleChannel)
}
}
}
通知重要级别如下:
展现形式 | 重要等级(8.0以上) |
---|---|
声音提示,显示浮动通知 | NotificationManager.IMPORTANCE_HIGH |
声音提示 | NotificationManager.IMPORTANCE_DEFAULT |
无声音提示 | NotificationManager.IMPORTANCE_LOW |
无声音提示,且不会在状态栏中显示 | NotificationManager.IMPORTANCE_MIN |
无声音提示,且不会在状态栏及通知栏中显示 | NotificationManager.IMPORTANCE_NONE |
创建通知渠道后,可以在App的信息中看到,如下图:
创建通知渠道分组
可以对不同的通知渠道进行分组管理,创建通知渠道分组示例代码如下:
ini
class NotificationExampleActivity : AppCompatActivity() {
......
private val notificationManagerCompat: NotificationManagerCompat by lazy { NotificationManagerCompat.from(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val applicationInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(0))
} else {
packageManager.getApplicationInfo(packageName, 0)
}
// 分组id
val systemNotificationGroupId = "system_notification_group"
// 分组显示名称
val systemNotificationGroupName = "${getText(applicationInfo.labelRes)} System Notification Group"
// 分组描述
val systemNotificationGroupDescription = "Receive all system-level notifications"
notificationManagerCompat.createNotificationChannelGroup(NotificationChannelGroupCompat.Builder(systemNotificationGroupId)
.setName(systemNotificationGroupName)
.setDescription(systemNotificationGroupDescription)
.build())
val messageNotificationGroupId = "message_notification_group"
val messageNotificationGroupName = "${getText(applicationInfo.labelRes)} Message Notification Group"
val messageNotificationGroupDescription = "Receive all message notifications"
notificationManagerCompat.createNotificationChannelGroup(NotificationChannelGroupCompat.Builder(messageNotificationGroupId)
.setName(messageNotificationGroupName)
.setDescription(messageNotificationGroupDescription)
.build())
// 渠道id
val systemErrorChannelId = "system_error_notification_channel"
// 渠道显示名称
val systemErrorChannelDisplayName = "${getText(applicationInfo.labelRes)} System Error Notification Channel"
// 渠道的重要性级别
val systemErrorChannelImportance = NotificationManager.IMPORTANCE_HIGH
val systemErrorChannel = NotificationChannel(systemErrorChannelId, systemErrorChannelDisplayName, systemErrorChannelImportance).apply {
// 渠道描述
description = "Receive system error notifications"
group = systemNotificationGroupId
}
notificationManagerCompat.createNotificationChannel(systemErrorChannel)
val friendMessageChannelId = "friend_message_notification_channel"
val friendMessageChannelDisplayName = "${getText(applicationInfo.labelRes)} Friend Message Notification Channel"
val friendMessageChannelImportance = NotificationManager.IMPORTANCE_DEFAULT
val friendMessageChannel = NotificationChannel(friendMessageChannelId, friendMessageChannelDisplayName, friendMessageChannelImportance).apply {
// 渠道描述
description = "Receive friend message notifications"
group = messageNotificationGroupId
}
notificationManagerCompat.createNotificationChannel(friendMessageChannel)
}
}
}
创建通知渠道分组后,可以在App的信息中看到,如下图:
调整通知渠道设置
用户可以在设置中对App的通知渠道进行调整,那么就有可能会关闭了我们期望用户接受的通知。可以通过NotificationManagerCompat.getNotificationChannelCompat()
方法获取通知渠道对象并检测其属性,若不符合预期,可以对用户解释通知的用途,让用户进入渠道设置页面调整。示例代码如下:
kotlin
class NotificationExampleActivity : AppCompatActivity() {
......
private val notificationManagerCompat: NotificationManagerCompat by lazy { NotificationManagerCompat.from(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
......
binding.btnCheckChannelSetting.setOnClickListener {
notificationManagerCompat.getNotificationChannelCompat(systemErrorChannelId)?.apply {
if (importance == NotificationManager.IMPORTANCE_MIN) {
intentLauncher.launch(Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
putExtra(Settings.EXTRA_CHANNEL_ID, id)
})
}
}
}
}
}
}
效果如图:
删除通知渠道
可以通过NotificationManagerCompat.deleteNotificationChannel()
删除已经配置的通知渠道,示例代码如下:
kotlin
class NotificationExampleActivity : AppCompatActivity() {
......
private val notificationManagerCompat: NotificationManagerCompat by lazy { NotificationManagerCompat.from(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
......
binding.btnDeleteChannel.setOnClickListener {
notificationManagerCompat.deleteNotificationChannel(friendMessageChannelId)
// 也可以通过id删除渠道分组
// notificationManagerCompat.deleteNotificationChannelGroup(messageNotificationGroupId)
}
}
}
}
效果如图:
创建基础通知
创建基础通知示例代码如下:
kotlin
class NotificationExampleActivity : AppCompatActivity() {
......
private val notificationManagerCompat: NotificationManagerCompat by lazy { NotificationManagerCompat.from(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......
binding.btnCreateBasicNotification.setOnClickListener {
if (notificationEnable()) {
val notificationBuilder = NotificationCompat.Builder(this, "system_error_notification_channel")
// 设置小图标(必须设置,否则会引起崩溃)
.setSmallIcon(R.drawable.notification)
// 设置通知标题
.setContentTitle("Example title")
// 设置通知内容
.setContentText("This is a basic notification example.")
// 设置是否自动取消
.setAutoCancel(false)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
// Android 7.1以下通知渠道配置的优先级无效,需通过setPriority()设置通知优先级
notificationBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT)
}
// 通知id,可以记录下来,后续可以通过通知id对通知进行操作
val notificationId = 0
notificationManagerCompat.notify(notificationId, notificationBuilder.build())
}
}
}
}
通知优先级如下:
展现形式 | 优先级(那7.1以下) |
---|---|
声音提示,显示浮动通知 | NotificationCompat.PRIORITY_HIGH或NotificationCompat.PRIORITY_MAX |
声音提示 | NotificationCompat.PRIORITY_DEFAULT |
无声音提示 | NotificationCompat.PRIORITY_LOW |
无声音提示,且不会在状态栏中显示 | NotificationCompat.PRIORITY_MIN |
效果如图:
完整示例代码
所有演示代码已在示例Demo中添加。