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

相关推荐
圆号本昊23 分钟前
Flutter Android Live2D 2026 实战:模型加载 + 集成渲染 + 显示全流程 + 10 个核心坑( OpenGL )
android·flutter·live2d
冬奇Lab1 小时前
ANR实战分析:一次audioserver死锁引发的系统级故障排查
android·性能优化·debug
冬奇Lab1 小时前
Android车机卡顿案例剖析:从Binder耗尽到单例缺失的深度排查
android·性能优化·debug
ZHANG13HAO2 小时前
调用脚本实现 App 自动升级(无需无感、允许进程中断)
android
圆号本昊3 小时前
【2025最新】Flutter 加载显示 Live2D 角色,实战与踩坑全链路分享
android·flutter
小曹要微笑4 小时前
MySQL的TRIM函数
android·数据库·mysql
mrsyf5 小时前
Android Studio Otter 2(2025.2.2版本)安装和Gradle配置
android·ide·android studio
DB虚空行者5 小时前
MySQL恢复之Binlog格式详解
android·数据库·mysql
liang_jy7 小时前
Android 事件分发机制(一)—— 全流程源码解析
android·面试·源码