【UI篇9】关于Android通知Notification的使用

744 阅读14分钟

1 通知简介

1.1 通知元素

image.png

  1. 通知icon:必选,通过setSmallIcon进行设置
  2. App Name:必选,不可自定义,由系统提供
  3. 时间戳:显示通知的发送时间,由系统提供,可以使用setWhen替换或者使用setShowWhen隐藏
  4. 大图标:可选,通过setLargeIcon进行设置
  5. 标题:通过setContentTitle设置
  6. 内容:通过setContentText设置
  7. 大图:可选,用于更多的信息展示
  8. 进度条:可选
  9. 按钮:可选,最多支持三个
  10. 通知优先级:由setPriority设置,优先级决定了通知在Android7.1及更低版本上的干扰程度。对于Android8.0及更高版本,请改为渠道重要性
  11. 样式模板:由setStyle添加样式模板来启动可展开的通知
  12. 通道及重要性:通过createNotificationChannel创建通道

1.2 通知管理

image.png

以微信举例,通知管理的信息有:

1.2.1 通道

通道:【新消息通知】【其他通知】【音视频通话邀请通知】即是其创建的通道名,通道的创建需要注意以下事项:

  1. 通知必须定义承载的通道才能进行发送。
  2. 每个通知通道可以设置不同的提醒方式和优先级,用户可以独立管理
  3. 准确的通道命名和合适的优先级定义可以让通知通道的管理更加简单,用户更容易理解

1.2.2 设置重要性级别

渠道重要性会影响以下位置发布的所有通知的干扰级别: 频道使用以下任一方法在 NotificationChannel 构造函数中指定该对象。 五个重要性级别 IMPORTANCE_NONE(0) 更改为 IMPORTANCE_HIGH(4)

如需支持搭载 Android 7.1(API 级别 25)或更低版本的设备,您还必须: 致电 setPriority() 并使用 NotificationCompat 类。

重要性 (NotificationManager.IMPORTANCE_*) 和优先级 (NotificationCompat.PRIORITY_*) 常量映射到用户可见的重要性 选项,如下表所示。

用户可见的重要性级别重要性(Android 8.0 及更高版本)优先级(Android 7.1 及更低版本)
紧急 :发出提示音,并以浮动通知的形式显示。IMPORTANCE_HIGHPRIORITY_HIGH 或 PRIORITY_MAX
:发出提示音。IMPORTANCE_DEFAULTPRIORITY_DEFAULT
不发出声音。IMPORTANCE_LOWPRIORITY_LOW
不发出提示音,且不会显示在状态栏中。IMPORTANCE_MINPRIORITY_MIN
不发出提示音,且不会显示在状态栏或通知栏中。IMPORTANCE_NONEN/A

无论重要程度如何,所有通知都会显示在非干扰系统中 界面位置

1.2.3 通知的重要程度

在搭载 Android 8.0(API 级别 26)及更高版本的设备上,通知的重要程度由通知发布到的渠道的 importance 决定。用户可以在系统设置中更改通知渠道的重要性,如图所示。

在搭载 Android 8.0 及更高版本的设备上,用户可以更改每个渠道的重要程度。

可能的重要性级别和相关的通知行为如下:

  • 紧急:发出提示音,并以提醒式通知的形式显示。
  • 高:发出提示音。
  • 中:不发出提示音。
  • 低:不发出提示音,且不会在状态栏中显示。

无论重要性如何,所有通知都会在不间断的系统界面位置显示,例如在抽屉式通知栏中显示,以及在启动器图标上作为标志显示。不过,您可以修改通知标记的外观

2 创建通知

2.1 声明运行时权限

2.2 设置通知内容

var builder = NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.notification_icon)
        .setContentTitle("My notification")
        .setContentText("Much longer text that cannot fit one line...")
         .setStyle(NotificationCompat.BigTextStyle()
                .bigText("Much longer text that cannot fit one line..."))
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)

2.3 创建渠道并设置重要性

在Android8.0及更高版本上传送通知,请先向系统注册应用的通知渠道,方法是将NotificationChannel的实例传递给createNotificationChannel()

private fun createNotificationChannel() {
    // Create the NotificationChannel, but only on API 26+ because
    // the NotificationChannel class is not in the Support Library.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val name = getString(R.string.channel_name)
        val descriptionText = getString(R.string.channel_description)
        val importance = NotificationManager.IMPORTANCE_DEFAULT
        val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
            description = descriptionText
        }
        // Register the channel with the system.
        val notificationManager: NotificationManager =
            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }
}

NotificationChannel 构造函数需要一个 importance,它会使用 NotificationManager 类中的一个常量。此参数确定出现任何属于此渠道的通知时如何打断用户。使用 setPriority() 设置优先级,以支持 Android 7.1 及更低版本,如上例所示。

2.4 设置通知的点按操作

// Create an explicit intent for an Activity in your app.
val intent = Intent(this, AlertDetails::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)

val builder = NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.notification_icon)
        .setContentTitle("My notification")
        .setContentText("Hello World!")
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        // Set the intent that fires when the user taps the notification.
         .setContentIntent(pendingIntent)
        .setAutoCancel(true)

调用setAutoCancel,它会在用户点按通知后自动移除通知

2.5 显示通知

with(NotificationManagerCompat.from(this)) {
    if (ActivityCompat.checkSelfPermission(
            this@MainActivity,
            Manifest.permission.POST_NOTIFICATIONS
        ) != PackageManager.PERMISSION_GRANTED
    ) {
        // TODO: Consider calling
        // ActivityCompat#requestPermissions
        // here to request the missing permissions, and then overriding
        // public fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>,
        //                                        grantResults: IntArray)
        // to handle the case where the user grants the permission. See the documentation
        // for ActivityCompat#requestPermissions for more details.

        return@with
    }
    // notificationId is a unique int for each notification that you must define.
    notify(NOTIFICATION_ID, builder.build())
}

2.6 添加操作按钮

一个通知最多可以提供三个操作按钮,让用户能够快速响应,例如暂停提醒或回复短信。但是,这些操作按钮不得重复用户点按通知时执行的操作。

需添加操作按钮,请将 PendingIntent 传递给 addAction() 方法。这就像在设置通知的默认点按操作,不同的是不会启动 activity,而是可以完成其他任务,例如启动在后台执行作业的 BroadcastReceiver,这样该操作就不会干扰已经打开的应用。

val ACTION_SNOOZE = "snooze"

val snoozeIntent = Intent(this, MyBroadcastReceiver::class.java).apply {
    action = ACTION_SNOOZE
    putExtra(EXTRA_NOTIFICATION_ID, 0)
}
val snoozePendingIntent: PendingIntent =
    PendingIntent.getBroadcast(this, 0, snoozeIntent, 0)
val builder = NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.notification_icon)
        .setContentTitle("My notification")
        .setContentText("Hello World!")
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        .setContentIntent(pendingIntent)
         .addAction(R.drawable.ic_snooze, getString(R.string.snooze),
                snoozePendingIntent) 

2.7 添加直接回复操作

如需创建支持直接回复的通知操作,请按以下步骤操作:

  1. 创建可添加到通知操作的 RemoteInput.Builder 实例。此类的构造函数接受系统用作文本输入键的字符串。您的应用稍后会使用该键检索输入的文本。
  // Key for the string that's delivered in the action's intent.
  private val KEY_TEXT_REPLY = "key_text_reply"
  var replyLabel: String = resources.getString(R.string.reply_label)
  var remoteInput: RemoteInput = RemoteInput.Builder(KEY_TEXT_REPLY).run {
      setLabel(replyLabel)
      build()
  }
  

2.8 添加进度条

通知可以包含动画形式的进度指示器,向用户显示正在进行的操作的状态。

操作期间的进度条

如果您可以估算操作在任何时间点的完成进度,应通过调用 setProgress(max, progress, false) 使用指示器的“确定性”形式(如图 5 所示)。第一个参数是“完成”值,例如 100。第二个是完成了多少。last 表示这是确定进度条。

随着操作的继续,持续使用 progress 的更新值调用 setProgress(max, progress, false) 并重新发出通知,如以下示例所示。

val builder = NotificationCompat.Builder(this, CHANNEL_ID).apply {
    setContentTitle("Picture Download")
    setContentText("Download in progress")
    setSmallIcon(R.drawable.ic_notification)
    setPriority(NotificationCompat.PRIORITY_LOW)
}
val PROGRESS_MAX = 100
val PROGRESS_CURRENT = 0
NotificationManagerCompat.from(this).apply {
    // Issue the initial notification with zero progress.
    builder.setProgress(PROGRESS_MAX, PROGRESS_CURRENT, false)
    notify(notificationId, builder.build())

    // Do the job that tracks the progress here.
    // Usually, this is in a worker thread.
    // To show progress, update PROGRESS_CURRENT and update the notification with:
    // builder.setProgress(PROGRESS_MAX, PROGRESS_CURRENT, false);
    // notificationManager.notify(notificationId, builder.build());

    // When done, update the notification once more to remove the progress bar.
    builder.setContentText("Download complete")
            .setProgress(0, 0, false)
    notify(notificationId, builder.build())
}

2.9 更新通知

如需在发出通知后对其进行更新,请再次调用 NotificationManagerCompat.notify(),并将之前使用的具有同一 ID 的通知传递给该方法。如果之前的通知被关闭,系统会改为创建新通知。

您可以选择性调用 setOnlyAlertOnce(),这样通知只会在通知首次显示时(以声音、振动或视觉提示)打断用户,以后更新时就不再显示。

2.10 其他

其他内容详见官网:通知

原则:点击本应用通知需要拉起本应用

3 通知创建

3.1 基本通知的创建

3.2 展开式通知的创建

要让图片仅在通知显示时以缩略图的形式显示, 如下图所示,系统会调用 setLargeIcon() 并将图片传递给它。然后,调用 BigPictureStyle.bigLargeIcon() 并向其传递 null,这样大图标会在通知发出时消失 展开:

val notification = NotificationCompat.Builder(context, CHANNEL_ID)
        .setSmallIcon(R.drawable.new_post)
        .setContentTitle(imageTitle)
        .setContentText(imageDescription)
         .setLargeIcon(myBitmap)
        .setStyle(NotificationCompat.BigPictureStyle()
                .bigPicture(myBitmap)
                 .bigLargeIcon(null))
        .build()

3.3 收件箱式通知的创建

应用 NotificationCompat.InboxStyle 如果您想添加多个简短的摘要行,例如 收到的电子邮件的摘要。这样,您就可以将多段内容 每个文字都截断为一行,而不是变成一行连续的文字 由NotificationCompat.BigTextStyle提供。

要添加新行,请调用 addLine() 最多六次,如以下示例所示。如果您要添加 6 个以上的 则只有前六行可见。

val notification = NotificationCompat.Builder(context, CHANNEL_ID)
        .setSmallIcon(R.drawable.baseline_email_24)
        .setContentTitle("5 New mails from Frank")
        .setContentText("Check them out")
        .setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.logo))
        .setStyle(
                NotificationCompat.InboxStyle()
                .addLine("Re: Planning")
                .addLine("Delivery on its way")
                .addLine("Follow-up")
        )
        .build()

3.4 对话框式通知的创建

val message1 = NotificationCompat.MessagingStyle.Message(
        messages[0].getText(),
        messages[0].getTime(),
        messages[0].getSender())
val message2 = NotificationCompat.MessagingStyle.Message(
        messages[1].getText(),
        messages[1].getTime(),
        messages[1].getSender())
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
        .setSmallIcon(R.drawable.new_message)
         .setStyle(
                NotificationCompat.MessagingStyle(resources.getString(R.string.reply_name))
                .addMessage(message1)
                .addMessage(message2))
        .build()

3.5 媒体控件式通知的创建

从Android 11开始,系统将所有的媒体通知收拢为一个固定的媒体播放面板。

应用 MediaStyleNotificationHelper.MediaStyle 显示媒体播放控件和曲目信息。

请指定与 MediaSession(在 构造函数。这样,Android 便能正确显示 媒体。

致电 addAction() 显示最多五个图标按钮。致电setLargeIcon() 设置专辑封面。

与其他通知样式不同,MediaStyle 还允许您修改 通过指定三个操作按钮来同时显示展开前尺寸的内容视图, 。为此,请提供操作按钮索引 setShowActionsInCompactView()

以下示例展示了如何创建带有媒体控件的通知:

val notification = NotificationCompat.Builder(context, CHANNEL_ID)
        // Show controls on lock screen even when user hides sensitive content.
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
         // Add media control buttons that invoke intents in your media service
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
        // Apply the media style template.
        .setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build()

3.6 自定义通知的创建

如果您需要自定义布局, NotificationCompat.DecoratedCustomViewStyle 。您可以使用此 API 为内容提供自定义布局。 通常由标题和文本内容占据,同时仍然使用 通知图标、时间戳、辅助文本和操作按钮的装饰。

此 API 的运作方式与展开式通知模板类似,都是以基本通知为基础构建而成。 布局如下所示:

  1. 构建基本通知 替换为 NotificationCompat.Builder
  2. 致电 setStyle()、 并向其传递 NotificationCompat.DecoratedCustomViewStyle
  3. 将自定义布局扩充为 RemoteViews
  4. 致电 setCustomContentView() 设置收起状态通知的布局。
  5. 您还可以选择调用 setCustomBigContentView() 为展开式通知设置不同的布局。
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

// Get the layouts to use in the custom notification.
val notificationLayout = RemoteViews(packageName, R.layout.notification_small)
val notificationLayoutExpanded = RemoteViews(packageName, R.layout.notification_large)

// Apply the layouts to the notification.
val customNotification = NotificationCompat.Builder(context, CHANNEL_ID)
        .setSmallIcon(R.drawable.notification_icon)
         .setStyle(NotificationCompat.DecoratedCustomViewStyle())
        .setCustomContentView(notificationLayout)
        .setCustomBigContentView(notificationLayoutExpanded)
        .build()

notificationManager.notify(666, customNotification)

4 通知组

从 Android 7.0(API 级别 24)开始,您可以在一个通知组中显示相关通知。例如,如果您的应用会显示收到的电子邮件的通知,请将有关新电子邮件的所有通知放在同一群组中,使其收起在一起。

image.png 如果您的用例满足以下所有条件,请使用通知组:

  • 子通知为完整通知,且可以单独显示,无需群组摘要。

  • 单独显示子级通知有一个好处。例如:

    • 它们是可操作的,具体操作特定于每条通知。
    • 每条通知中都包含供用户查看的更多信息。

如果您的通知不符合上述条件,请考虑使用新信息更新现有通知,或创建消息样式的通知,在同一会话中显示多项更新。

  1. 当同应用通知多于2条时,通知会自动折叠为一组;
  2. 组通知下仅显示2条概览,仅会显示标题和内容;
  3. 用户点击组通知即可展开;

4.1 创建通知组并为其添加通知

如需创建通知组,请为该通知组定义一个唯一标识符字符串。然后,针对您想要添加到通知组中的每条通知,调用 setGroup() 并传入通知组名称。例如:

KotlinJava

val GROUP_KEY_WORK_EMAIL = "com.android.example.WORK_EMAIL"

val newMessageNotification = NotificationCompat.Builder(this@MainActivity, CHANNEL_ID)
        .setSmallIcon(R.drawable.new_mail)
        .setContentTitle(emailObject.getSenderName())
        .setContentText(emailObject.getSubject())
        .setLargeIcon(emailObject.getSenderAvatar())
         .setGroup(GROUP_KEY_WORK_EMAIL)
        .build()

默认情况下,系统会根据通知的发布时间对其进行排序,但您可以通过调用 setSortKey() 来更改通知顺序。

如果通知组的提醒必须由其他通知处理,请调用 setGroupAlertBehavior()。例如,如果您只希望通知组摘要发出提醒,那么通知组中的所有子级都必须具有通知组提醒行为 GROUP_ALERT_SUMMARY。其他选项包括 GROUP_ALERT_ALL 和 GROUP_ALERT_CHILDREN

4.2 设置通知组摘要

分组通知必须包含一条额外的通知来充当组摘要。如需启用分组通知,您必须设置组摘要。此群组摘要必须包含群组中每个其他通知中的部分文本,以帮助用户了解群组中的内容。群组摘要的显示方式取决于 Android 版本:

  • 在低于 7.0(API 级别 24)的 Android 版本(无法显示嵌套的通知组)上,系统只会显示您的组摘要通知,并隐藏所有其他通知。用户可以点按群组摘要通知来打开您的应用。
  • 在 Android 7.0 及更高版本中,系统会将通知组摘要通知显示为一组嵌套的通知,并用每个分组通知中的文本片段进行标记。它不会显示您在群组摘要通知中设置的文本。用户可以展开嵌套的通知组,以查看该组中的各个通知,如图 1 所示。

即使较新版本的 Android 没有显示您设计的通知组摘要文本,您始终需要手动设置摘要才能启用分组通知。通知组摘要的行为在某些设备类型(例如穿戴式设备)上可能会有所不同。在通知组摘要中设置丰富内容有助于在所有设备和版本上提供最佳体验。

如需添加通知组摘要,请按以下步骤操作:

  1. 使用通知组说明创建新通知 - 通常最好使用收件箱样式的通知来实现此目的。
  2. 通过调用 setGroup() 将摘要通知添加到通知组中。
  3. 通过调用 setGroupSummary(true) 指定将其用作通知组摘要。

以下代码展示了创建组摘要的示例:

// Use constant ID for notifications used as group summary.
val SUMMARY_ID = 0
val GROUP_KEY_WORK_EMAIL = "com.android.example.WORK_EMAIL"

val newMessageNotification1 = NotificationCompat.Builder(this@MainActivity, CHANNEL_ID)
        .setSmallIcon(R.drawable.ic_notify_email_status)
        .setContentTitle(emailObject1.getSummary())
        .setContentText("You will not believe...")
        .setGroup(GROUP_KEY_WORK_EMAIL)
        .build()

val newMessageNotification2 = NotificationCompat.Builder(this@MainActivity, CHANNEL_ID)
        .setSmallIcon(R.drawable.ic_notify_email_status)
        .setContentTitle(emailObject2.getSummary())
        .setContentText("Please join us to celebrate the...")
        .setGroup(GROUP_KEY_WORK_EMAIL)
        .build()

val summaryNotification = NotificationCompat.Builder(this@MainActivity, CHANNEL_ID)
        .setContentTitle(emailObject.getSummary())
        // Set content text to support devices running API level < 24.
        .setContentText("Two new messages")
        .setSmallIcon(R.drawable.ic_notify_summary_status)
        // Build summary info into InboxStyle template.
        .setStyle(NotificationCompat.InboxStyle()
                .addLine("Alex Faarborg Check this out")
                .addLine("Jeff Chang Launch Party")
                .setBigContentTitle("2 new messages")
                .setSummaryText("janedoe@example.com"))
        // Specify which group this notification belongs to.
         .setGroup(GROUP_KEY_WORK_EMAIL)
        // Set this notification as the summary for the group.
         .setGroupSummary(true)
        .build()

NotificationManagerCompat.from(this).apply {
    notify(emailNotificationId1, newMessageNotification1)
    notify(emailNotificationId2, newMessageNotification2)
    notify(SUMMARY_ID, summaryNotification)
}

摘要通知 ID 必须保持不变,以便其仅发布一次;这样,如果摘要信息发生更改,您就可以之后再对其进行更新。后续在通知组中添加通知会使现有摘要发生更新。

如需查看使用通知的示例代码,请参阅 Android 通知示例

4.3 自动分组

在 Android 7.0(API 级别 24)及更高版本中,如果您的应用发送通知,但未指定组键或组摘要,则系统可能会自动将它们归为一组。自动分组的通知会显示一个群组摘要通知,其中包含一些分组通知中的文本摘要。用户可以展开此摘要通知,查看每条单独的通知,就像手动分组的通知一样。

自动分组行为在某些设备类型上可能会有所不同。为确保在所有设备和版本上提供最佳体验,如果您知道通知必须分组,请指定组键和组摘要,以确保它们分组显示。

5 通知问题排查

5.1 查看通知管理

查看通知管理设置是否正确

5.2 搜索关键字

Notification notification_enqueue 1、contentView=null 表示普通通知,使用系统通知模板,非自定义通知

6 通知相关问题注意点

6.1 问题1 跳转到对应的Activity

要跳转过来,intent需要配置category才行

<intent-filter>
    <action android:name="pictorial.intent.ACTION_SET_FESTIVAL_WALLPAPER" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

6.2 问题2 通知免解锁跳转到Activity

【解决方法】必须是自定义通知才能免解锁跳转到Activity,点击通知启动Receiver->Activity

  1. 安全性:在未解锁设备的状态下启动Activity可能会带来安全问题,特别是涉及敏感信息的Activity。通过自定义通知,可以确保只有用户明确选择后才进行跳转,降低安全风险。
  2. 系统限制:Android系统默认限制在锁定屏幕上启动Activity,以防止未经授权的应用在设备上执行潜在有害操作。自定义通知配合PendingIntent可以让系统信任操作请求,从而允许在特定条件下启动Activity。