使用 Android 通知

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

简介

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

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

通知可以及时传送信息,还可以包含可让用户执行快速操作(例如发送回复或暂停闹钟)的按钮。点击通知后,用户会转到与通知内容相关的应用视图。

通过通知功能,您可以提醒用户执行重要任务、告知用户所发生的事情,或者让其在应用处于后台运行时传达重要信息。谨慎使用通知。这样做不仅尊重用户,而且更有可能让您的应用收到应有的关注。

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

您应当已掌握的内容

您应熟悉以下内容/操作:

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

学习内容

  • 如何创建通知、设置通知样式以及发送通知。
  • 如何取消通知。
  • 如何创建通知渠道。
  • 如何向通知添加快速操作功能?
  • 如何在应用图标上显示通知标志。

您将执行的操作

  • 向起始应用添加通知。
  • 取消您之前发送的通知。
  • 为不同类型的通知创建渠道。
  • 在起始应用中自定义通知。
  • 添加快速操作即可让您的通知具有互动性。
  • 关闭通知标志。

鸡蛋烹饪非常简单,但如果您无法追踪时间,则可能是一项具有挑战性的任务。在此 Codelab 中,您将开发一个鸡蛋计时器应用,让它变得完美,就像您将来的鸡蛋一样。您将首先构建一个工作的鸡蛋计时器应用,该应用可让用户为不同的鸡蛋样式设置不同的烹饪时间设置。计时器从所选的时间间隔开始倒计时,并在鸡蛋准备就绪后显示消息框消息。

这看起来可能很实用,但实际上还算不上完美,而且不太方便用户操作。首先,消息框消息仅显示较短的时间,因此很容易被忽略。此外,如果应用不在前台运行,或者设备处于锁定状态,那么消息框消息消失后,就无法显示计时器状态的视觉指示器。

鸡蛋计时器最好使用通知来提醒用户时间到了。用户一定要知道鸡蛋已经做好了,否则鸡蛋会煮熟!通知是可视的,可以包含声音并使设备振动 - 所有方式都可以吸引用户的注意力!这样,您就可以获得完美的鸡蛋,吃得开心,而且享受美食。

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

从 GitHub 克隆代码库并切换到 starter 分支。

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


或者,您也可以下载 Zip 文件并将其解压缩,然后在 Android Studio 中打开代码库。

下载 Zip 文件

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

您将看到一张鸡蛋图片和一个下拉菜单,其中包含烹饪鸡蛋的预定义时间间隔列表。点击软煮下拉菜单的三角形。列表中的第一个选项用于测试目的,并且将闹钟设置为仅 10 秒。列表旁边是一个启动鸡蛋计时器的开关。您随时可以使用此开关启动和停止鸡蛋计时器。起始代码完全有效,这意味着你可以设置鸡蛋计时器,看看它是否倒计时至 0。计时器完成后,系统会显示一条消息框消息,如下所示。

  1. 检查源代码。起始应用由一个名为 MainActivity 的 Activity 组成。有三个名为 receiveruiutil 的子软件包。

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

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

第 1 步:创建基本通知

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

  1. 打开 NotificationUtils.kt 类并找到 TODO: Step 1.1。在本 Codelab 和应用代码中,您将找到匹配的待办事项。
  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。由于您的应用在给定时间内仅有一条有效通知,因此您可以为所有通知使用相同的 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 是一种系统服务,提供为 Notifications 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. 从设置列表中选择通知。您应该会在显示通知设置正下方看到一个名为鸡蛋的新频道。

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

第 3 步:向应用添加通知

到目前为止,这显示了 Notifications API 的基本用法,但在启动计时器后立即发送通知并没有什么意义。用户更喜欢在鸡蛋准备好时收到通知。在此 Codelab 的以下部分中,您将修复此问题,并将消息框消息更改为通知。

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

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

  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. (可选)移除消息框,因为系统会在计时器到来时您的应用发送通知。
// 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.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 具有用于取消所有有效通知(名为 cancelAll())的 API。

  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() 设置通知的默认点按操作,不同的是,您可以启动其他各种操作,例如,启动在后台执行作业的 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。用户点按“延后”按钮 60 秒后,系统将使用此 PendingIntent 设置新闹钟来发布新通知。
  4. 第一个参数是此 PendingIntent 应在其中启动 Activity 的应用上下文。
  5. 第二个参数是请求代码,即此待处理 intent 的请求代码。如果您需要更新或取消此待定 intent,则需要使用此代码访问待定 intent。
  6. 接下来,添加 snoozeIntent 对象,该对象是要启动的 activity 的 intent。
  7. 最后,添加 #FLAG_ONE_SHOT 的标志值,因为 intent 将仅使用一次。该快速操作和通知会在首次点按后消失,因此该 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) 这 5 个重要级别中选择一个。您为渠道指定的重要性级别会应用到您在其中发布的所有通知消息。

渠道重要性级别

用户可见的重要性级别

重要性(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. 再次运行应用,启动计时器并观察应用图标。您应该不会在应用图标上看到任何标记。

解决方案代码位于已下载代码的主分支中。

Udacity 课程:

Android 开发者文档:

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