使用 Android 通知

此 Codelab 是“使用 Kotlin 进行高级 Android 开发”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘课程的价值,但并不强制要求这样做。“使用 Kotlin 进行高级 Android 开发”Codelab 着陆页列出了所有课程 Codelab。

简介

通知是指在应用界面之外向用户显示的消息。如果设备处于解锁状态,通知会显示在屏幕顶部;如果设备处于锁定状态,通知会显示在锁定屏幕上,具体取决于安全设置。

典型的通知包含标题、说明和图标。通知还可以包含可点击的操作、快速回复、可扩展的内容和图片。

通知可以及时传递内容,并且可以包含按钮,让用户能够执行快速操作,例如发送回复或延后闹钟。点击通知后,用户会进入应用中与通知内容相关的视图。

当应用在后台运行时,通知可用于提醒用户执行重要任务、告知用户发生了某些事情,或传达用户需要立即了解的重要信息。谨慎使用通知。这不仅尊重了用户,还更有可能让您的应用通知获得应有的关注。

在此 Codelab 中,您将学习如何在 Android 应用中创建和使用通知。

您应当已掌握的内容

您应熟悉以下内容:

  • 如何使用 Kotlin 创建 Android 应用。尤其是使用 Android SDK。
  • 如何使用架构组件和数据绑定来设计应用。
  • 对 BroadcastReceiver 有基本的了解
  • 对 AlarmManager 有基本的了解

学习内容

  • 如何创建、设置样式和发送通知。
  • 如何取消通知。
  • 如何创建通知渠道。
  • 如何向通知添加快捷操作。
  • 如何在应用图标上显示通知标记。

您将执行的操作

  • 向 starter 应用添加通知。
  • 取消您之前发送的通知。
  • 为不同类型的通知创建渠道。
  • 在初始应用中自定义通知。
  • 添加快捷操作,让通知更具互动性。
  • 关闭通知标记。

煮鸡蛋很简单,但如果您不注意时间,可能会遇到挑战。在此 Codelab 中,您将开发一个鸡蛋计时器应用,并使其像您未来的鸡蛋一样完美。您将从一个可正常运行的鸡蛋计时器应用开始,该应用可让用户为不同烹饪方式的鸡蛋设置不同的烹饪时间。计时器会从所选时间间隔开始倒计时,并在鸡蛋煮好时显示消息框。

这可能看起来很实用,但远非完美,而且不太方便用户使用。首先,Toast 消息仅显示很短的时间,因此很容易错过。此外,如果应用不在前台或设备已锁定,则在 Toast 消息消失后,计时器的状态不会有任何视觉指示。

理想情况下,计时器应使用通知来告知用户时间到了。用户确实需要立即知道鸡蛋是否煮好,否则鸡蛋就会煮过头! 通知是可视的,可以包含声音,还可以使设备振动,所有这些方式都可以吸引用户的注意力!这样一来,您就可以做出完美的鸡蛋,让用户满意而归。

如需获取示例应用,您可以执行以下任一操作:

从 GitHub 克隆代码库,然后切换到 starter 分支。

$  git clone https://github.com/googlecodelabs/android-kotlin-notifications


或者,您也可以下载 ZIP 文件形式的代码库,将其解压缩并在 Android Studio 中打开。

下载 Zip 文件

  1. 在 Android Studio 中打开并运行应用。

您会看到一个鸡蛋图片和一个下拉菜单,其中列出了预定义的煮蛋时间间隔。点击软煮下拉菜单对应的三角形。列表中的第一个选项用于测试目的,会将闹钟设置为仅响铃 10 秒。列表旁边有一个开关,用于启动计时器。您可以使用此开关随时开始和停止计时。起始代码可完全正常运行,这意味着您可以设置鸡蛋计时器,并观看它倒计时到 0。当计时器结束时,系统会显示一条 Toast 消息,如下所示。

  1. 检查源代码。起始应用包含一个名为 MainActivity 的 activity。有三个子软件包,名称分别为 receiveruiutil

  • /receiver - receiver 软件包包含两个名为 AlarmReceiverSnoozeReceiver 的广播接收器。AlarmReceiverAlarmManager 触发,以便在用户定义的计时器到时发送通知。SnoozeReceiver 处理用户点击以暂停通知。
  • /ui - 包含 EggTimerFragment,它是应用界面部分的一部分。EggTimerViewModel 负责启动和取消计时器以及其他与生命周期相关的应用任务。
  • /util - 此软件包中有两个文件。BindingUtils.kt 具有绑定适配器,可在应用界面与 ViewModel 之间实现数据绑定。NotificationUtils.kt 具有针对 NotificationManager 的扩展方法。

使用通知是吸引用户关注应用的绝佳方式。无论应用是否正在运行或在前台运行,通知都会在屏幕顶部显示一个弹出式窗口,并且可能包含声音或振动。如需创建通知,您需要使用通知构建器并提供标题文字、内容文字和图标。当 builder 包含所有必需字段后,系统服务 NotificationManager 会帮助您将此内容显示为通知。NotificationManager 负责发送通知、更新其内容和取消通知。在接下来的步骤中,您将向 NotificationManager 添加扩展方法。这样,每次需要使用 NotificationManager 时,您都可以使用这些扩展函数来实现所需的功能。

第 1 步:创建基本通知

在此任务中,您将创建新通知、为用户设置消息,并发送通知

  1. 打开 NotificationUtils.kt 类并找到 TODO: Step 1.1。您会在本 Codelab 和应用代码中找到匹配的 TODO。
  2. 检查给定的 sendNotification() 函数。您将扩展此扩展函数,使其能够通过 NotificationManager 发送通知。
//NotificationUtils.kt
// TODO: Step 1.1 extension function to send messages (GIVEN)
/**
 * Builds and delivers a notification.
 *
 * @param messageBody, notification text.
 * @param context, activity context.
 */
fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) {
  1. 获取通知构建器的实例,传入应用上下文和渠道 ID。频道 ID 是频道的字符串值。

通知渠道是一种对通知进行分组的方式。通过将类似类型的通知分组在一起,开发者和用户可以控制渠道中的所有通知。创建渠道后,即可使用该渠道传送任意数量的通知。

//NotificationUtils.kt
// TODO: Step 1.2 get an instance of NotificationCompat.Builder
val builder = NotificationCompat.Builder(
        applicationContext,
        applicationContext.getString(R.string.egg_notification_channel_id)
)
  1. 设置通知图标以表示您的应用、标题以及您想向用户显示的消息的内容文本。在此 Codelab 中,您将看到更多用于进一步自定义通知的选项,但这是发送通知所需设置的最少数据量。
//NotificationUtils.kt
   // TODO: Step 1.3 set title, text and icon to builder
   .setSmallIcon(R.drawable.cooked_egg)
   .setContentTitle(applicationContext.getString(R.string.notification_title))
   .setContentText(messageBody)
  1. 接下来,您需要使用通知的唯一 ID 和来自构建器的 Notification 对象来调用 notify()

此 ID 表示当前通知实例,更新或取消此通知时需要用到。由于您的应用在任意给定时间只会有一条有效通知,因此您可以为所有通知使用同一 ID。为此,您已在 NotificationUtils.kt 中获得一个名为 NOTIFICATION_ID 的常量。请注意,由于您是从同一类中的扩展函数执行调用,因此可以直接调用 notify()

//NotificationUtils.kt
   // TODO: Step 1.4 call notify to send the notification
    // Deliver the notification
    notify(NOTIFICATION_ID, builder.build())
  1. 打开 ui/EggTimerViewModel.kt 并找到 startTimer() 函数。当用户启用计时器时,此函数会创建具有所选时间间隔的闹钟。
  2. 当用户启动计时器时,您将在此函数中触发通知。为了调用您之前实现的 sendNotification() 函数,您需要一个 NotificationManager 实例。NotificationManager 是一种系统服务,可提供为通知 API 公开的所有功能,包括您添加的扩展函数。无论何时,只要您想发送、取消或更新通知,都需要向系统请求 NotificationManager 的实例。使用通知消息和上下文调用 sendNotification()| 函数。
// EggTimerViewModel.kt
// TODO: Step 1.5 get an instance of NotificationManager 
// and call sendNotification

val notificationManager = ContextCompat.getSystemService(
    app, 
    NotificationManager::class.java
) as NotificationManager
                notificationManager.sendNotification(app.getString(R.string.timer_running), app)

您已经非常接近目标了。不过,如果您现在运行应用并设置计时器,则不会收到通知。

  1. 打开 logcat 并搜索 "No Channel found"。您应该会看到一条错误消息,指出 egg_channel 不存在。在以下步骤中,您将详细了解通知渠道并修复此问题。

第 2 步:通知渠道

从 API 级别 26 开始,所有通知都必须分配到相应的渠道。如果您点按并按住应用启动器图标,选择“应用信息”,然后点按“通知”,您会看到与该应用关联的通知渠道列表。目前,该列表为空,因为您的应用尚未创建任何渠道。

渠道代表一种通知“类型” - 例如,您的鸡蛋计时器可以在鸡蛋煮熟时发送通知,还可以使用另一个渠道发送每日通知,提醒您在早餐时吃鸡蛋。一个渠道中的所有通知都会归为一组,用户可以为整个渠道配置通知设置。这样一来,用户就可以根据自己感兴趣的通知类型来个性化设置通知。例如,用户可以停用早餐通知,但仍选择接收来自计时器的通知。

开发者可以设置要应用于渠道中所有通知的初始设置、重要性和行为。设置初始设置后,用户可以覆盖这些设置。

在第 1.1 步中,您使用了 egg_notification_channel_id 作为通知渠道,因此现在需要实际创建并自定义此渠道的通知设置和行为。

  1. 打开 EggTimerFragment.kt 并找到 createChannel() 函数。
  2. 将唯一频道 ID 传递给 NotificationChannel 的构造函数。
  3. 传递通知渠道名称,用户还会在设置界面中看到该名称。
  4. 作为最后一个参数,传递通知渠道的重要性级别。此 Codelab 稍后将介绍重要性级别,因此您现在可以使用 NotificationManager.IMPORTANCE_LOW
  5. notificationChannel 对象上,将 enableLights 设置为 true。此设置会在显示通知时启用指示灯。
  6. notificationChannel 对象中,将 lightColor 设置为红色,以便在显示通知时显示红灯。
  7. notificationChannel 对象上,将 enableVibration 设置为 true 以启用振动。
  8. notificationChannel 对象上,将频道说明设置为 ‘Time for breakfast'。
  9. 通过调用 getSystemService() 获取 NotificationManager 的实例。
  10. NotificationManager 调用 createNotificationChannel() 并传递您在上一步中创建的 notificationChannel 对象。
//EggTimerFragment.kt
private fun createChannel(channelId: String, channelName: String) {
    // TODO: Step 1.6 START create a channel
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val notificationChannel = NotificationChannel(
            channelId,
            channelName,
            // TODO: Step 2.4 change importance
            NotificationManager.IMPORTANCE_LOW
        )
        // TODO: Step 2.6 disable badges for this channel

        notificationChannel.enableLights(true)
        notificationChannel.lightColor = Color.RED
        notificationChannel.enableVibration(true)
        notificationChannel.description = "Time for breakfast"

        val notificationManager = requireActivity().getSystemService(
            NotificationManager::class.java
        )
        notificationManager.createNotificationChannel(notificationChannel)
    }
    // TODO: Step 1.6 END create channel
}
  1. 接下来,如需创建渠道,您需要调用刚刚编写的 createChannel() 函数(步骤 1.7)。此函数接受两个参数,即频道 ID 和频道名称。您需要从项目中已提供的字符串资源中查找频道 ID 和频道名称。
// EggTimerFragment.kt
    // TODO: Step 1.7 call createChannel
    createChannel(
          getString(R.string.egg_notification_channel_id),
          getString(R.string.egg_notification_channel_name)
    )
  1. 您需要将渠道 ID 传递给通知构建器。您已在第 1.2 步中完成此操作。如果将错误的渠道 ID 设置为值,通知将会失败。打开 NotificationUtils.kt,验证您之前设置的频道 ID 是否正确。
// NotificationUtils.kt
val builder = NotificationCompat.Builder(
        applicationContext,
       // TODO: Step 1.8 verify the notification channel name
        applicationContext.getString(R.string.egg_notification_channel_id)
)
  1. 运行应用,您会看到每次启动计时器时,应用都会发送通知。
  2. 拉下状态栏,观察通知标题、内容和图标是否与您在上一步中设置的一致。
  3. 如需验证新创建的频道,请关闭应用并找到应用图标。长按应用图标,然后选择应用信息

  1. 从设置列表中选择通知。您应该会在显示通知设置的正下方看到一个名为 Egg 的新渠道。

现在,当您运行应用时,系统会显示通知。作为应用开发者,您可以自定义此渠道上发送的所有通知的设置和行为,您的用户也可以这样做。恭喜,您已创建通知!

第 3 步:向应用添加通知

到目前为止,我们展示了通知 API 的基本用法,但在启动计时器后立即发送通知并没有多大意义。用户可能更希望在蛋孵化完成时收到通知。在此 Codelab 的下一部分中,您将修复此问题并将 Toast 消息更改为通知。

您已发送通知并观察了通知向用户的显示方式,但这只是创建出色通知的第一步。在此步骤中,您将更改通知的发送时间,使其更合适。

您的应用使用 AlarmManager 设置闹钟。与 AlarmManager 相关的代码已在起始代码中给出,并用于显示消息框消息。AlarmManager 会跟踪所需的时间选择,并在时间到达时触发 AlarmReceiver.ktonReceive() 函数。如果您打开 AlarmReceiver.kt 并前往 onReceive(),您应该会看到每次设置计时器时显示的 Toast 消息。

  1. 打开 AlarmReceiver.ktNotificationManager 的实例),并使用消息文本和上下文参数调用 sendNotification() 函数。
// AlarmReceiver.kt
   // TODO: Step 1.9 add call to sendNotification
   val notificationManager = ContextCompat.getSystemService(
       context, 
       NotificationManager::class.java
   ) as NotificationManager
             
   notificationManager.sendNotification(
       context.getText(R.string.eggs_ready).toString(), 
       context
   )
  1. (可选)移除 Toast,因为应用会在计时器到时发送通知。
// AlarmReceiver.kt
     // TODO: Step 1.10 [Optional] remove toast
//   Toast.makeText(
//       context, 
//       context.getText(R.string.eggs_ready),
//       Toast.LENGTH_SHORT
//   ).show()
  1. 运行应用。每次启动计时器和每次计时器结束时,您都应该会看到通知。

这并不理想。您不希望向用户发送过多的通知。您可以移除用户开始计时器时发送的第一个通知。

  1. 打开 EggTimerFragment.kt 并移除第 1.5 步的通知代码。
// EggTimeViewModel.kt

// TODO: Step 1.5 get an instance of NotificationManager 
// and call sendNotification
// val notificationManager = ContextCompat.getSystemService(
//      app,
//      NotificationManager::class.java
// ) as NotificationManager
// notificationManager.sendNotification(app.getString(R.string.eggs_ready), app)
  1. 再次运行应用。
  2. 设置计时器,将其置于后台,然后等待时间结束。您会看到一条通知。这是一种更有用的通知。

第 4 步:添加内容 intent

  1. 再次运行应用(如果该应用尚未运行)。
  2. 点击此通知。毫无反应!

显示通知并告知用户固然很好,但当用户点击通知时,他们希望返回到相应的应用。在此 Codelab 部分中,您将向通知添加 intent,以便将用户带回计时器界面。

Intent 是一种消息传递对象,可用于向其他应用组件请求操作。Intent 可用于启动 activity、服务或传递广播。在这种情况下,您可以使用此 intent 来告知系统在用户点按通知时打开 MainActivity。由于您的应用仅包含一个视图,因此您在此处没有太多选项。不过,在较大的应用中,通知应通过将用户带到适合其与通知互动的界面来打造顺畅的体验。

  1. 打开 NotificationUtils.kt 并找到 sendNotification() 扩展函数。
  2. 使用您的 applicationContext 和要启动的 activity MainActivity::class.java 创建 Intent
// NotificationUtils.kt

fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) {
    // Create the content intent for the notification, which launches
    // this activity
   // TODO: Step 1.11 create intent
    val contentIntent = Intent(applicationContext, MainActivity::class.java)

您创建了 intent,但通知显示在您的应用之外。若要使 intent 在应用之外正常运行,您需要创建一个新的 PendingIntent

PendingIntent 授予其他应用或系统代表您的应用执行操作的权限。PendingIntent 本身只是对系统维护的令牌的引用,该令牌描述了用于检索它的原始数据。这意味着,即使其所属应用的进程被终止,PendingIntent 本身仍可供已获得它的其他进程使用。在这种情况下,无论计时器应用是否正在运行,系统都会使用待处理 intent 代表您打开相应应用。

  1. 创建包含 applicationContextNOTIFICATION_ID、您在上一步中创建的 contentIntentPendingIntent 标志的 PendingIntentPendingIntent 标志用于指定是创建新的 PendingIntent 还是使用现有的 PendingIntent。您需要将 PendingIntent.FLAG_UPDATE_CURRENT 设置为标志,因为您不希望在已有通知的情况下创建新通知。这样,您将修改与您提供的 intent 相关联的当前 PendingIntent
// NotificationUtils.kt
   // TODO: Step 1.12 create PendingIntent
    val contentPendingIntent = PendingIntent.getActivity(
        applicationContext, 
        NOTIFICATION_ID,
        contentIntent,
        PendingIntent.FLAG_UPDATE_CURRENT
    )
  1. PendingIntent 传递给通知。您可以通过对 NotificationBuilder 调用 setContentIntent() 来执行此操作。现在,当您点击通知时,系统会触发 PendingIntent,打开 MainActivity
  2. 同时将 setAutoCancel() 设置为 true,以便在用户点按通知时,通知会自行关闭,同时将用户带到应用中。
// NotificationUtils.kt
    // TODO: Step 1.13 set content intent
    .setContentIntent(contentPendingIntent)
    .setAutoCancel(true)
  1. 再次运行应用。
  2. 设置计时器,将应用置于后台,然后等待通知显示。
  3. 看到通知后,下拉状态栏,然后点击通知,观察应用如何被调入前台。

第 5 步:取消通知

您有一个可正常运行的带通知功能的计时器,但存在一个小问题。如果您设置了计时器,收到通知,然后再次设置计时器,则在新的计时器运行期间,之前的通知会保留在状态栏中。如果应用在后台运行,这可能会让用户感到困惑,并可能导致鸡蛋煮得不够熟。

若要解决此问题,您需要在开始新的计时器时清除之前的通知。首先,在 NotificationUtils.kt 中创建另一个扩展函数。NotificationManager 具有一个用于取消所有有效通知的 API,名为 cancelAll()

  1. 打开 NotificationsUtil.kt
  2. NotificationManager 上添加一个调用 cancelAll() 的扩展函数。
// NotificationUtils.kt

// TODO: Step 1.14 Cancel all notifications
/**
 * Cancels all notifications.
 *
 */
fun NotificationManager.cancelNotifications() {
    cancelAll()
}
  1. 打开 EggTimerViewModel.kt,然后前往 startTimer() 函数。
  2. startTimer() 内,从系统获取 NotificationManager 的实例并调用 cancelNotifications()
//  EggTimerViewModel.kt
   //TODO Step 1.15 call cancel notification
    val notificationManager =
       ContextCompat.getSystemService(
            app,
            NotificationManager::class.java
        ) as NotificationManager
    notificationManager.cancelNotifications()       
  1. 运行应用并启动计时器。
  2. 看到通知后,再次启动计时器,并观察我们的应用如何自动从状态栏中删除之前的通知。

通知框架为开发者提供了多种自定义选项,以便他们根据需要设置自定义操作和样式。在此任务中,您将学习如何自定义煮蛋器通知。

第 1 步:设置通知样式

根据您的需求和通知内容设置通知样式,可让通知更加醒目,看起来更像是应用的扩展。通知框架附带了多种内置样式,可为您提供帮助,您也可以随时创建自己的样式。

NotificationCompat 提供以下内置样式:

  • BigTextStyle,可以显示大段文字,例如在展开时显示电子邮件的内容。
  • BigPictureStyle,显示包含大型图片附件的大格式通知。
  • InboxStyle,显示对话风格的文本内容。
  • MediaStyle,用于显示媒体播放控件。
  • MessagingStyle,可显示包含任意数量的人员之间的多条消息的大格式通知。

如需详细了解其他样式,请参阅创建展开式通知文档。在此步骤中,您将使用 NotificationCompat.BigPictureStyle 创建一个可展开的通知,该通知在展开时会显示一张大鸡蛋图片。

  1. 打开 NotificationUtils.kt 并找到 sendNotification() 函数。
  2. 首先,使用 BitmapFactoryresources 加载图片。
// NotificationUtils.kt

// TODO: Step 2.0 add style
val eggImage = BitmapFactory.decodeResource(
     applicationContext.resources, 
     R.drawable.cooked_egg
)
  1. 创建新的 BigPictureStyle 并设置映像。
  2. bigLargeIcon() 设置为 null,这样大图标就会在通知展开时消失。
// NotificationUtils.kt

// TODO: Step 2.0 add style
val eggImage = BitmapFactory.decodeResource(
     applicationContext.resources, 
     R.drawable.cooked_egg
)
val bigPicStyle = NotificationCompat.BigPictureStyle()
        .bigPicture(eggImage)
        .bigLargeIcon(null)
  1. 将样式设置为 setStyle(),值为 bigPicStyle
  2. 使用 setLargeIcon() 将大图标设置为 eggImage,这样当通知收起时,图片将显示为较小的图标。
// NotificationUtils.kt
// TODO: Step 2.1 add style to builder
.setStyle(bigPicStyle)
.setLargeIcon(eggImage)
  1. 运行应用并设置计时器。首次显示通知时,通知会以收起状态显示在抽屉式通知栏中。如果您展开通知,扩展的通知区域会显示一张大图片。

第 2 步:通知操作

通知操作是您可以添加到通知中的另一种自定义设置。目前,当用户点击通知时,系统会将他们重定向到您的应用。除了这种默认的通知操作之外,您还可以添加可在通知中完成与应用相关任务的操作按钮。

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

如需添加操作按钮,请将 PendingIntent 传递给构建器上的 addAction() 函数。这类似于通过调用 setContentIntent() 设置通知的默认点按操作,不同的是不会启动 activity,而是可以完成各种其他任务,例如启动在后台执行作业的 BroadcastReceiver,这样该操作就不会干扰已经打开的应用。

在此 Codelab 中,您已经获得了一个名为 SnoozeReceiverBoadcastReceiver。您将使用 SnoozeReceiver 接收用户对通知操作的点击。在以下步骤中,您将添加代码,以便在用户点击“打盹”操作按钮时,将鸡蛋计时器通知设为打盹 60 秒。当用户点击“延后”操作时,SnoozeReceiver 将收到 intent,并创建一个新闹钟,以便在 60 秒后发送新通知。

  1. 打开 SnoozeReceiver.kt。此类与您之前使用的 AlarmReceiver 类似。在以下步骤中,您将添加代码来触发 SnoozeReceiveronReceive() 函数。简而言之,SnoozeReceiver 中的代码将创建一个新闹钟,以便在一分钟后发送新通知。向下滚动到 onReceive 函数的底部,从系统获取 notificationManager 的实例,然后调用 cancelAll。
// SnoozeReceiver.kt
        val notificationManager = ContextCompat.getSystemService(
            context,
            NotificationManager::class.java
        ) as NotificationManager
        notificationManager.cancelAll()
  1. 如需使用 SnoozeReceiver,请打开 NotificationUtils.kt
  2. sendNotification() 函数中的样式之后,立即为 SnoozeReceiver 创建新的 Intent snoozeIntent
  3. 通过对 PendingIntent 调用 getBroadcast() 方法来创建待处理 intent,该方法需要以下步骤中的参数。系统将使用此 PendingIntent 设置一个新闹钟,以便在用户点按“打盹”按钮后 60 秒发布新通知。
  4. 第一个参数是 PendingIntent 应在其中启动 activity 的应用上下文。
  5. 第二个参数是请求代码,即相应待处理 intent 的请求代码。如果您需要更新或取消此待处理 intent,则需要使用此代码来访问该待处理 intent。
  6. 接下来,添加 snoozeIntent 对象,即要启动的 activity 的 intent。
  7. 最后,添加标志值 #FLAG_ONE_SHOT,因为 intent 只会使用一次。快速操作和通知会在首次点按后消失,因此意图只能使用一次。
// NotificationUtils.kt

// TODO: Step 2.2 add snooze action
val snoozeIntent = Intent(applicationContext, SnoozeReceiver::class.java)
val snoozePendingIntent: PendingIntent = PendingIntent.getBroadcast(
    applicationContext, 
    REQUEST_CODE, 
    snoozeIntent, 
    FLAGS
)
  1. 接下来,对 notificationBuilder 调用 addAction() 函数。此函数需要一个图标和一段文字来向用户说明您的操作。您还需要添加 snoozeIntent。当用户点击您的操作时,系统将使用此 intent 来触发正确的 boadcastReceiver
// NotificationUtils.kt
// TODO: Step 2.3 add snooze action
.addAction(
    R.drawable.egg_icon, 
    applicationContext.getString(R.string.snooze),
    snoozePendingIntent
)
  1. 运行“鸡蛋计时器”应用以测试“打盹”操作。
  2. 运行计时器并将应用置于后台。当计时器时间到时,展开通知,您会看到通知现在有一个“暂停”操作按钮,可将鸡蛋计时器暂停一分钟。

第 3 步:通知的重要程度

重要程度决定了通知应在多大程度上干扰用户(视觉上和听觉上)。重要程度越高的通知对用户的干扰程度就越高。

您必须在 NotificationChannel 构造函数中指定重要性级别。您最初为计时器应用设置了较低的重要性。您可以使用从 IMPORTANCE_NONE(0)IMPORTANCE_HIGH(4) 的五个重要性级别之一。您为渠道指定的重要性级别会应用到您在其中发布的所有通知消息。

渠道重要性级别

用户可见的重要性级别

重要性(Android 8.0 及更高版本)

优先级(Android 7.1 及更低版本)

发出提示音,并以浮动通知的形式显示(在屏幕顶部弹出)

IMPORTANCE_HIGH

PRIORITY_HIGH / PRIORITY_MAX

发出提示音

IMPORTANCE_DEFAULT

PRIORITY_DEFAULT

无声

IMPORTANCE_LOW

PRIORITY_LOW

不发出提示音,且不会在状态栏中显示

IMPORTANCE_MIN

PRIORITY_MIN

如需了解如何选择适当的优先级,请参阅通知设计指南中的“优先级”部分。为应用中的通知选择重要程度时,您应谨慎考虑。选择渠道重要程度时,应考虑用户的时间和注意力。当不重要的通知伪装成紧急通知时,可能会发出不必要的警报,让人分心。用户可以完全控制通知的重要程度,因此如果您创建的通知令人反感,他们可以完全关闭您的通知渠道。

在步骤 1.6 中首次创建通知时,由于该通知旨在不打扰用户,因此将计时器设置为发送低优先级的通知。不过,最好在鸡蛋煮过头之前引起用户的注意。如需更改通知的重要程度级别,请先从渠道设置入手。渠道重要性会影响在渠道中发布的所有通知的干扰级别,因此必须在 NotificationChannel 构造函数中指定渠道重要性。

  1. 如需更改应用通知渠道的重要性级别,请打开 EggTimerFragment.kt 并前往 createChannel()。将重要程度从 IMPORTANCE_LOW 更改为 IMPORTANCE_HIGH
// EggTimerFragment.kt
    val notificationChannel = NotificationChannel(
        channelId,
        channelName,
        // TODO: Step 2.4 change importance
        NotificationManager.IMPORTANCE_HIGH
    )

如需支持搭载 Android 7.1(API 级别 25)或更低版本的设备,您还必须使用 NotificationCompat 类中的优先级常量针对每条通知调用 setPriority()

  1. 打开 NotificationUtils.kt,并将以下内容添加到通知构建器对象。
// NotificationUtils.kt
   .addAction(
       R.drawable.common_google_signin_btn_icon_dark,
       applicationContext.getString(R.string.snooze),
       snoozePendingIntent
    )
   // TODO: Step 2.5 set priority
    .setPriority(NotificationCompat.PRIORITY_HIGH)
  1. 在运行应用之前,请在设备或模拟器上长按应用图标,然后选择“卸载”以清除之前的渠道设置。如果您未能成功卸载应用,渠道优先级设置将不会更改,并且在发布通知时也不会出现任何行为变化。
  2. 现在,再次运行应用并启动计时器。这次,当通知送达时,无论应用是在前台还是后台运行,您都应该会在屏幕顶部看到一个弹出式窗口。

第 4 步:通知标记

通知标志是当关联的应用有有效通知时,显示在相应应用的启动器图标上的小圆点。用户可以长按应用图标以显示通知。

这些圆点(称为标志)默认会显示,您的应用无需执行任何操作。不过,在某些情况下,您的通知并不适合显示标志,您可以通过对 NotificationChannel 对象调用 setShowBadge(false),针对每个渠道停用标志。由于鸡蛋计时器在给定时间只能有一个有效通知,因此应用图标上的标记对用户来说没有太大好处。在以下步骤中,您将停用标记,仅显示计时器的通知。

  1. 向鸡蛋计时器的频道创建代码添加 setShowBadge(false) 以停用徽章。
// EggTimerFragment.kt

    ).apply {
        // TODO: Step 2.6 disable badges for this channel
        setShowBadge(false)
    }
  1. 再次运行应用,启动计时器,然后观察应用图标。您应该不会在应用图标上看到任何标记。

解决方案代码位于所下载代码的 master 分支中。

Udacity 课程:

Android 开发者文档:

如需本课程中其他 Codelab 的链接,请参阅“使用 Kotlin 进行高级 Android 开发”Codelab 着陆页。