Ongoing Activity

在 Wear OS 中,将正在进行的活动与正在进行的通知配对,可将该通知添加到 Wear OS 用户界面中的其他界面。这让用户可以更长时间地参与到长时间运行的活动中。

正在进行的通知通常用于指示通知具有用户正在积极参与或以某种方式挂起并因此占用设备的后台任务。

例如,Wear OS 用户可能会使用健身应用记录活动中的跑步,然后离开该应用开始其他任务。当用户离开健身应用时,该应用会转换为与某个后台工作绑定的正在进行的通知,以让用户了解他们的跑步情况。该通知会向用户提供更新,并提供一个方便的方式点击返回应用。

然而,要查看通知,用户必须从表盘下方滑入通知托盘并找到正确的通知。这不如在其他界面上方便。

借助 Ongoing Activity API,应用的正在进行的通知可以将信息公开到 Wear OS 上多个新的、方便的界面,以保持用户的参与。

例如,在这个健身应用中,信息可以作为可点击的跑步图标显示在用户的表盘上。

running-icon

图 1. 活动指示器。

全局应用启动器的最近使用部分也列出了所有正在进行的活动。

launcher

图 2. 全局启动器。

以下是使用与正在进行的活动绑定的正在进行的通知的良好情况:

timer

图 3. 计时器:主动倒计时,并在计时器暂停或停止时结束。

map

图 4. 逐向导航:宣布到目的地的方向。当用户到达目的地或停止导航时结束。

music

图 5. 媒体:在整个会话期间播放音乐。在用户暂停会话后立即结束。

Wear 会自动为媒体应用创建正在进行的活动。

有关为其他类型的应用创建正在进行的活动的深入示例,请参阅Ongoing Activity codelab

设置

要在您的应用中开始使用 Ongoing Activity API,请将以下依赖项添加到您的应用的 build.gradle 文件中:

dependencies {
  implementation "androidx.wear:wear-ongoing:1.0.0"
  // Includes LocusIdCompat and new Notification categories for Ongoing Activity.
  implementation "androidx.core:core:1.6.0"
}

启动正在进行的活动

首先创建正在进行的通知,然后创建正在进行的活动。

创建正在进行的通知

正在进行的活动与正在进行的通知密切相关。它们协同工作,向用户告知用户正在积极参与的任务,或以某种方式挂起并因此占用设备的任务。

您必须将正在进行的活动与正在进行的通知配对。将您的正在进行的活动与通知关联有很多好处,包括以下几点:

  • 通知是不支持正在进行的活动的设备上的备用方案。当您的应用在后台运行时,通知是应用显示的唯一界面。
  • 在 Android 11 及更高版本上,当应用在其他界面上显示为正在进行的活动时,Wear OS 会在通知托盘中隐藏通知。
  • 当前实现使用 Notification 本身作为通信机制。

使用 Notification.Builder.setOngoing 创建正在进行的通知。

启动正在进行的活动

拥有正在进行的通知后,按照以下示例创建正在进行的活动。查看随附的注释以了解每个属性的行为。

Kotlin

var notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
      
      .setSmallIcon(..)
      .setOngoing(true)

val ongoingActivityStatus = Status.Builder()
    // Sets the text used across various surfaces.
    .addTemplate(mainText)
    .build()

val ongoingActivity =
    OngoingActivity.Builder(
        applicationContext, NOTIFICATION_ID, notificationBuilder
    )
        // Sets the animated icon that will appear on the watch face in
        // active mode.
        // If it isn't set, the watch face will use the static icon in
        // active mode.
        .setAnimatedIcon(R.drawable.ic_walk)
        // Sets the icon that will appear on the watch face in ambient mode.
        // Falls back to Notification's smallIcon if not set.
        // If neither is set, an Exception is thrown.
        .setStaticIcon(R.drawable.ic_walk)
        // Sets the tap/touch event so users can re-enter your app from the
        // other surfaces.
        // Falls back to Notification's contentIntent if not set.
        // If neither is set, an Exception is thrown.
        .setTouchIntent(activityPendingIntent)
        // Here, sets the text used for the Ongoing Activity (more
        // options are available for timers and stopwatches).
        .setStatus(ongoingActivityStatus)
        .build()

ongoingActivity.apply(applicationContext)

notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())

Java

NotificationCompat.Builder notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
      
      .setSmallIcon(..)
      .setOngoing(true);

OngoingActivityStatus ongoingActivityStatus = OngoingActivityStatus.Builder()
    // Sets the text used across various surfaces.
    .addTemplate(mainText)
    .build();

OngoingActivity ongoingActivity =
    OngoingActivity.Builder(
        applicationContext, NOTIFICATION_ID, notificationBuilder
    )
        // Sets the animated icon that will appear on the watch face in
        // active mode.
        // If it isn't set, the watch face will use the static icon in
        // active mode.
        .setAnimatedIcon(R.drawable.ic_walk)
        // Sets the icon that will appear on the watch face in ambient mode.
        // Falls back to Notification's smallIcon if not set.
        // If neither is set, an Exception is thrown.
        .setStaticIcon(R.drawable.ic_walk)
        // Sets the tap/touch event so users can re-enter your app from the
        // other surfaces.
        // Falls back to Notification's contentIntent if not set.
        // If neither is set, an Exception is thrown.
        .setTouchIntent(activityPendingIntent)
        // Here, sets the text used for the Ongoing Activity (more
        // options are available for timers and stopwatches).
        .setStatus(ongoingActivityStatus)
        .build();

ongoingActivity.apply(applicationContext);

notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());

以下步骤指出了前面示例中最重要的一部分:

  1. NotificationCompat.Builder 上调用 .setOngoing(true) 并设置任何可选字段。

  2. 创建 OngoingActivityStatus — 或另一个状态选项,如下一节所述 — 以表示文本。

  3. 创建 OngoingActivity 并设置通知 ID。

  4. 使用上下文在 OngoingActivity 上调用 apply()

  5. 调用 notificationManager.notify() 并传入与正在进行的活动中设置的相同通知 ID,以将它们关联起来。

状态

您可以使用 StatusOngoingActivity 的当前实时状态公开给用户在新的界面上,例如启动器的最近使用部分。要使用此功能,请使用 Status.Builder 子类。

在大多数情况下,您只需要添加一个模板,该模板表示您希望显示在应用启动器最近使用部分中的文本。

然后,您可以使用 spans 通过 addTemplate() 方法并指定文本的任何动态部分作为 Status.Part 来自定义文本的显示方式。

以下示例显示了如何使“time”一词显示为红色。该示例使用 Status.StopwatchPart 来表示应用启动器最近使用部分中的秒表。

Kotlin

val htmlStatus =
        "<p>The <font color=\"red\">time</font> on your current #type# is #time#.</p>"

val statusTemplate =
        Html.fromHtml(
                htmlStatus,
                Html.FROM_HTML_MODE_COMPACT
        )

// Creates a 5 minute timer.
// Note the use of SystemClock.elapsedRealtime(), not System.currentTimeMillis().
val runStartTime = SystemClock.elapsedRealtime() + TimeUnit.MINUTES.toMillis(5)

val status = new Status.Builder()
   .addTemplate(statusTemplate)
   .addPart("type", Status.TextPart("run"))
   .addPart("time", Status.StopwatchPart(runStartTime)
   .build()

Java

String htmlStatus =
        "<p>The <font color=\"red\">time</font> on your current #type# is #time#.</p>";

Spanned statusTemplate =
        Html.fromHtml(
                htmlStatus,
                Html.FROM_HTML_MODE_COMPACT
        );

// Creates a 5 minute timer.
// Note the use of SystemClock.elapsedRealtime(), not System.currentTimeMillis().
Long runStartTime = SystemClock.elapsedRealtime() + TimeUnit.MINUTES.toMillis(5);

Status status = new Status.Builder()
   .addTemplate(statusTemplate)
   .addPart("type", new Status.TextPart("run"))
   .addPart("time", new Status.StopwatchPart(runStartTime)
   .build();

要从模板中引用某个部分,请使用被 # 符号包围的名称。要在输出中生成 #,请在模板中使用 ##

前面的示例使用 HTMLCompat 生成一个 CharSequence 传递给模板,这比手动定义 Spannable 对象更容易。

其他自定义

除了 Status 之外,您还可以通过以下方式自定义正在进行的活动或通知。但是,这些自定义功能可能不会被使用,具体取决于 OEM 的实现。

正在进行的通知

  • 设置的类别决定了正在进行的活动的优先级。
    • CATEGORY_CALL来电或视频通话或类似的同步通信请求
    • CATEGORY_NAVIGATION地图或逐向导航
    • CATEGORY_TRANSPORT用于播放的媒体传输控制
    • CATEGORY_ALARM闹钟或计时器
    • CATEGORY_WORKOUT锻炼(新类别)
    • CATEGORY_LOCATION_SHARING临时位置共享(新类别)
    • CATEGORY_STOPWATCH秒表(新类别)

Ongoing Activity

  • 动画图标:黑白矢量图,最好带有透明背景。在活动模式下显示在表盘上。如果未提供动画图标,则使用默认通知图标。(默认通知图标因应用而异。)

  • 静态图标:带有透明背景的矢量图标。在环境模式下显示在表盘上。如果未设置动画图标,则静态图标会在活动模式下显示在表盘上。如果未提供此图标,则使用通知图标。如果两者均未设置,则会抛出异常。(应用启动器仍然使用应用图标。)

  • OngoingActivityStatus:纯文本或 Chronometer。显示在应用启动器的最近使用部分。如果未提供,则使用通知的“上下文文本”

  • 触摸意图:当用户点击正在进行的活动图标时,用于切换回应用的 PendingIntent。显示在表盘或启动器项上。它可以与用于启动应用的原始意图不同。如果未提供,则使用通知的内容意图。如果两者均未设置,则会抛出异常。

  • LocusId将启动器快捷方式分配给正在进行的活动所对应的 ID。当活动正在进行时,显示在启动器的最近使用部分。如果未提供,启动器会隐藏同一软件包中最近使用部分的所有应用项目,只显示正在进行的活动。

  • 正在进行的活动 ID:当一个应用程序有多个正在进行的活动时,用于消除对 fromExistingOngoingActivity() 调用歧义的 ID。

更新正在进行的活动

在大多数情况下,开发者在需要更新屏幕上的数据时会创建新的正在进行的通知和新的正在进行的活动。然而,Ongoing Activity API 也提供了辅助方法来更新 OngoingActivity,如果您想保留一个实例而不是重新创建它。

如果应用在后台运行,它可以向 Ongoing Activity API 发送更新。但是,不要过于频繁地执行此操作,因为更新方法会忽略彼此过于接近的调用。每分钟几次更新是合理的。

要更新正在进行的活动和已发布的通知,请使用您之前创建的对象并调用 update(),如下例所示:

Kotlin

ongoingActivity.update(context, newStatus)

Java

ongoingActivity.update(context, newStatus);

为方便起见,有一个静态方法可以创建正在进行的活动。

Kotlin

OngoingActivity.recoverOngoingActivity(context)
               .update(context, newStatus)

Java

OngoingActivity.recoverOngoingActivity(context)
               .update(context, newStatus);

停止正在进行的活动

当应用作为正在进行的活动运行结束时,它只需要取消正在进行的通知。

您也可以选择在通知或正在进行的活动进入前台时取消它们,然后在返回后台时重新创建它们,但这并非必需。

暂停正在进行的活动

如果您的应用有明确的停止操作,请在取消暂停后继续正在进行的活动。对于没有明确停止操作的应用,请在暂停时结束活动。

最佳实践

使用 Ongoing Activity API 时请记住以下几点:

  • 在调用 notificationManager.notify(...) 之前调用 ongoingActivity.apply(context)
  • 为您的 Ongoing Activity 设置一个静态图标,无论是显式设置,还是通过通知作为回退。否则,您将收到 IllegalArgumentException

  • 使用带有透明背景的黑白矢量图标。

  • 为您的正在进行的活动设置一个触摸意图,无论是显式设置,还是使用通知作为回退。否则,您将收到 IllegalArgumentException

  • 对于 NotificationCompat,请使用 Core AndroidX 库 core:1.5.0-alpha05+,其中包含 LocusIdCompat 和用于锻炼、秒表和位置共享的新类别

  • 如果您的应用在清单中声明了多个 MAIN LAUNCHER 活动,请发布一个动态快捷方式,并使用 LocusId 将其与您的正在进行的活动关联起来。

在 Wear OS 设备上播放媒体时发布媒体通知

如果在 Wear OS 设备上播放媒体内容,请发布媒体通知。这允许系统创建相应的正在进行的活动。

如果您正在使用 Media3,通知会自动发布。如果您手动创建通知,它应该使用 MediaStyleNotificationHelper.MediaStyle,并且相应的 MediaSession 应该填充其会话活动