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

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui