Android 通知(一)— 基础通知

通知是指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中添加。

ExampleDemo github

ExampleDemo gitee

相关推荐
大耳猫4 小时前
主动测量View的宽高
android·ui
帅次6 小时前
Android CoordinatorLayout:打造高效交互界面的利器
android·gradle·android studio·rxjava·android jetpack·androidx·appcompat
枯骨成佛7 小时前
Android中Crash Debug技巧
android
kim565912 小时前
android studio 更改gradle版本方法(备忘)
android·ide·gradle·android studio
咸芝麻鱼12 小时前
Android Studio | 最新版本配置要求高,JDK运行环境不适配,导致无法启动App
android·ide·android studio
无所谓จุ๊บ12 小时前
Android Studio使用c++编写
android·c++
csucoderlee13 小时前
Android Studio的新界面New UI,怎么切换回老界面
android·ui·android studio
kim565913 小时前
各版本android studio下载地址
android·ide·android studio
饮啦冰美式13 小时前
Android Studio 将项目打包成apk文件
android·ide·android studio
夜色。13 小时前
Unity6 + Android Studio 开发环境搭建【备忘】
android·unity·android studio