Android 通知(三)— 调整圆点提示与从通知打开Activity

836 阅读4分钟

在之前的文章中分别介绍了如何创建基础通知以及如何调整为可展开式的通知,本文将介绍如何调整圆点提示以及从通知打开Activity

前文指路:

圆点提示

接收通知时用户不一定正在使用手机,当用户继续使用手机时,圆点提示可以让用户直观的感受到App接收到了新消息。

从Android 8.0 开始,当App发出新的通知时,无需调用代码,桌面的App图标上就会显示通知标志(也就是圆点提示)。

下表为App发出通知时在不同API的设备上的表现:

Android 7.1.1Android 8.0
api25.pngapi26.png

可以看到在Android 8.0的设备上确实会有圆点提示。

启用或停用提示标志

通知可能有时要显示圆点,提示有时不显示圆点提示,可以将通知归类到一个通知渠道中,在创建通知渠道时通过NotificationChannel.setShowBadge()方法设置显示或隐藏标记。示例代码如下:

class NotificationExampleActivity : AppCompatActivity() {

    ......

    private val notificationManagerCompat: NotificationManagerCompat by lazy { NotificationManagerCompat.from(this) }

    ......

    // 分组id
    private val systemNotificationGroupId = "system_notification_group"

    // 渠道id
    private val enableBadgeSystemErrorChannelId = "enable_badge_system_error_notification_channel"
    private val disableBadgeSystemErrorChannelId = "disable_badge_system_error_notification_channel"
    private var currentSystemErrorChannelId = ""

    private val currentSystemErrorChannelIdKey = "currentSystemErrorChannelId"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ......

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            ......

            // 获取当前保存的渠道id
            // 默认关闭圆点提示
            currentSystemErrorChannelId = SpUtils.getString(currentSystemErrorChannelIdKey, disableBadgeSystemErrorChannelId) ?: disableBadgeSystemErrorChannelId
            if (notificationManagerCompat.getNotificationChannelCompat(currentSystemErrorChannelId) == null) {
                val systemErrorChannelDisplayName = "${getText(applicationInfo.labelRes)} System Error Notification Channel"
                val systemErrorChannelDescription = "Receive system error notifications"
                val systemErrorChannelImportance = NotificationManager.IMPORTANCE_HIGH
                createNotificationChannel(currentSystemErrorChannelId, systemErrorChannelDisplayName, systemErrorChannelDescription, systemErrorChannelImportance, currentSystemErrorChannelId == enableBadgeSystemErrorChannelId, systemNotificationGroupId)
            }

            ......
        }

        ......
        
        binding.btnEnableShowBadge.setOnClickListener {
            notificationManagerCompat.getNotificationChannelCompat(currentSystemErrorChannelId)?.apply {
                // 获取当前的系统信息通知渠道,如果圆点提示是禁用的,创建一个新的启用圆点提示的系统通知渠道
                if (!canShowBadge()) {
                    val currentChannelName = name?.toString() ?: ""
                    val currentChannelDescription = description ?: ""
                    val currentImportance = importance
                    val currentGroupId = group ?: ""
                    notificationManagerCompat.deleteNotificationChannel(id)
                    currentSystemErrorChannelId = enableBadgeSystemErrorChannelId
                    createNotificationChannel(currentSystemErrorChannelId, currentChannelName, currentChannelDescription, currentImportance, channelGroupId = currentGroupId)
                    SpUtils.put(currentSystemErrorChannelIdKey, currentSystemErrorChannelId)
                }
            }
        }
        binding.btnDisableShowBadge.setOnClickListener {
            notificationManagerCompat.getNotificationChannelCompat(currentSystemErrorChannelId)?.apply {
                // 获取当前的系统信息通知渠道,如果圆点提示是启用的,创建一个新的启用圆点提示的系统通知渠道
                if (canShowBadge()) {
                    val currentChannelName = name?.toString() ?: ""
                    val currentChannelDescription = description ?: ""
                    val currentImportance = importance
                    val currentGroupId = group ?: ""
                    notificationManagerCompat.deleteNotificationChannel(id)
                    currentSystemErrorChannelId = disableBadgeSystemErrorChannelId
                    createNotificationChannel(currentSystemErrorChannelId, currentChannelName, currentChannelDescription, currentImportance, false, currentGroupId)
                    SpUtils.put(currentSystemErrorChannelIdKey, currentSystemErrorChannelId)
                }
            }
        }
    }

    ......

    private fun createNotificationChannel(channelId: String, displayName: String, displayDescription: String, importance: Int, showBadge: Boolean = true, channelGroupId: String? = null) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            notificationManagerCompat.createNotificationChannel(NotificationChannel(channelId, displayName, importance).apply {
                description = displayDescription
                setShowBadge(showBadge)
                group = channelGroupId
            })
        }
    }
}

效果如图:

Screen_recording_202 -big-original.gif

注意: showBadge属性只能在创建时设置。删除通知渠道后使用相同id创建新通知渠道会恢复被删除的通知渠道配置(新的配置不生效)。

设置提示计数

除了简单的显示圆点提示之外,官方还提供了NotificationCompat.Builder.setNumber()方法用于设置圆点中显示的数字。示例代码如下:

class NotificationExampleActivity : AppCompatActivity() {

    ......

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ......
        
        binding.btnSetBadgeCount.setOnClickListener {
            if (notificationEnable()) {
                val notificationBuilder = NotificationCompat.Builder(this, currentSystemErrorChannelId)
                    .setSmallIcon(R.drawable.notification)
                    .setContentTitle("Set Badge Count")
                    .setContentText("Six notifications have been received")
                    // 设置圆点提示中显示的数字
                    .setNumber(6)
                    .setAutoCancel(false)
                val notificationId = 1004
                notificationManagerCompat.notify(notificationId, notificationBuilder.build())
            }
        }
    }

    ......
}

效果如图:

Pixel 7Xiaomi 14
Screen_recording_202 -original-original.gifScreen_recording_202 -original-original (2).gif

可以看到这个API在不同设备上的表现有所差异,设置圆点提示的数量还需要针对不同的设备进行适配,之后会继续调研看看是否有通用的方式来实现。

从通知打开Activity

用户点击通知时,可以根据通知的类型做出不同的响应,本文主要介绍用户点击通知后从通知打开Activity

常规方式打开Activity

假如通知打开的Activity是App中常规的Activity,那么为了良好的用户体验,需要在打开Activity的同时创建对应的返回堆栈,让用户返回时可以正常的返回上级页面。

在Manifest中定义Activity的结构

Manifest中通过android:parentActivityName设置每个Activity的父级Activity,示例代码如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    ......

    <application
        ......
        >

        ......

        <activity
            android:name=".androidapi.notification.NotificationExampleActivity"
            android:parentActivityName=".home.HomeActivity" />

        <activity
            android:name=".androidapi.notification.NotificationOpenActivitiesExample"
            android:parentActivityName=".androidapi.notification.NotificationExampleActivity" />
    </application>
</manifest>

创建有返回堆栈的PendingIntent

通过TaskStackBuilder来获取有返回堆栈的PendingIntent,然后在创建通知时使用该PendingIntent,示例代码如下:

class NotificationExampleActivity : AppCompatActivity() {

    ......

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ......
        
        binding.btnOpenActivities.setOnClickListener {
            val targetActivityIntent = Intent(this, NotificationOpenActivitiesExample::class.java)
            val targetPendingIntent = TaskStackBuilder.create(this).run {
                addNextIntentWithParentStack(targetActivityIntent)
                getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
            }
            if (notificationEnable()) {
                val notificationBuilder = NotificationCompat.Builder(this, currentSystemErrorChannelId)
                    .setSmallIcon(R.drawable.notification)
                    .setContentTitle("Open Activities")
                    .setContentText("Open target activity")
                    .setContentIntent(targetPendingIntent)
                val notificationId = 1005
                notificationManagerCompat.notify(notificationId, notificationBuilder.build())
            }
        }
    }

    ......
}

效果演示

如图:

Screen_recording_202 -big-original.gif

不带返回堆栈的打开Activity

有些通知打开的Activity是“一次性”的,无需返回堆栈。

在Manifest中设置

Manifest中配置列属性如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    ......

    <application
        ......
        >

        ......

        <!--taskAffinity 置空确保不会进入App的任务栈-->
        <!--excludeFromRecents 从最近使用的App中移除-->
        <activity
            android:name=".androidapi.notification.NotificationOpenActivityExample"
            android:excludeFromRecents="true"
            android:launchMode="singleTask"
            android:taskAffinity="" />
    </application>
</manifest>

获取PendingIntent

获取PendingIntent,然后在创建通知时使用该PendingIntent,示例代码如下:

class NotificationExampleActivity : AppCompatActivity() {

    ......

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ......
        
        binding.btnOpenSingleActivity.setOnClickListener {
            val targetActivityIntent = Intent(this, NotificationOpenActivityExample::class.java)
            val targetPendingIntent = PendingIntent.getActivity(this, 0, targetActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
            val notificationBuilder = NotificationCompat.Builder(this, currentSystemErrorChannelId)
                .setSmallIcon(R.drawable.notification)
                .setContentTitle("Open Activity")
                .setContentText("Open target activity")
                .setContentIntent(targetPendingIntent)
            val notificationId = 1006
            notificationManagerCompat.notify(notificationId, notificationBuilder.build())
        }
    }

    ......
}

效果演示

如图:

Screen_recording_202 -big-original.gif

完整示例代码

所有演示代码已在示例Demo中添加。

ExampleDemo github

ExampleDemo gitee