持续性活动

在 Wear OS 中,通过将持续性活动与持续性通知配对,可将相应通知添加到 Wear OS 界面中的其他 surface。这样,用户就可以与长时间进行的活动保持更高的互动度。

持续性通知通常用于表明,相应通知存在后台任务且用户正与之积极互动,或者存在正等待进行某种处理并因此占用设备资源的后台任务。

例如,Wear OS 用户可能使用某款锻炼应用记录进行活动时的跑步数据,然后离开该应用并启动一些其他任务。当用户离开锻炼应用时,该应用将转换为与某项后台工作相关联的持续性通知,以便让用户随时了解其跑步数据。通过该通知,用户可以了解最新锻炼数据,并能轻松地通过点按返回到锻炼应用。

不过,若要查看通知,用户必须滑动到表盘下方的通知栏中,然后找到相应通知。这样的操作方式不如其他 surface 方便。

利用 Ongoing Activity API,应用的持续性通知可以将信息显示在 Wear OS 中多个便捷的新 surface 中,以方便用户保持互动。

例如,在下面这款锻炼应用中,信息可在用户的表盘上显示为可点按的跑步图标:

跑步图标

图 1. 活动指示器。

全局应用启动器的“最近用过”部分也会列出所有持续性活动:

启动器

图 2. 全局启动器。

下列情况很适合使用与持续性活动相关联的持续性通知:

计时器

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

地图

图 4. 精细导航:播报前往目的地的路线。当用户到达目的地或停止导航时结束。

音乐

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

Wear 会自动为媒体应用创建持续性活动。

请参阅“持续性活动”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"
}

开始持续性活动

首先,请创建一个持续性通知,然后创建一个持续性活动。

创建持续性通知

持续性活动与持续性通知密切相关。二者共同发挥作用,让用户了解某项任务,并且用户正与该任务积极互动,或该任务正等待进行某种处理并因此占用设备资源。

您必须将持续性活动与持续性通知配对。将持续性活动与通知相关联有很多好处,其中包括:

  • 在不支持持续性活动的设备上,通知是后备方式。当应用在后台运行时,通知是您的应用显示的唯一 surface。
  • 在 Android 11 及更高版本中,当应用在其他 surface 上显示为持续性活动时,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, builder.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, builder.build());

下列步骤指出了上一个示例中最重要的部分:

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

  2. 创建一个 OngoingActivityStatus 或其他状态选项(如下一部分所述),以表示文本。

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

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

  5. 调用 notificationManager.notify() 并传入在持续性活动中设置的通知 ID,将二者关联起来。

状态

您可以使用 Status 在新 surface(例如启动器的“最近用过”部分)上向用户显示 OngoingActivity 的当前实时状态。如需使用此功能,请使用 Status.Builder 子类。

在大多数情况下,您只需要添加模板来表示想要在应用启动器的“最近用过”部分显示的文本。

然后,您可以通过 span 自定义文本外观:使用 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:秒表(新类别)

持续性活动

  • 动画图标:黑白矢量图标,最好为透明背景。在氛围模式下会显示在表盘上。如果未提供动画图标,系统会使用默认通知图标。每个应用的默认通知图标各不相同。

  • 静态图标:具有透明背景的矢量图标。在氛围模式下会显示在表盘上。如果未设置动画图标,系统会在活动模式期间在表盘上使用静态图标。如果未提供该图标,系统会使用通知图标。如果二者都未设置,系统会抛出异常。应用启动器仍会使用应用图标。

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

  • 触摸 intent:一个 PendingIntent,用于在用户点按持续性活动图标时切换回应用。会显示在表盘上或启动器项上。它可以与用于启动应用的原始 intent 不同。如果未提供该 intent,系统会使用通知的内容 intent。如果二者都未设置,系统会抛出异常。

  • LocusId该 ID 用于指定持续性活动对应的启动器快捷方式。在活动持续期间,会显示在启动器的“最近用过”部分。如果未提供该 ID,启动器会隐藏“最近用过”部分中来自相应软件包的所有应用项,只显示持续性活动。

  • 持续性活动 ID:当应用有多个持续性活动时,该 ID 用于消除 fromExistingOngoingActivity() 调用方面的歧义。

更新持续性活动

在大多数情况下,当开发者需要更新屏幕上的数据时,他们会创建一个新的持续性通知和一个新的持续性活动。不过,如果您想要保留实例而不是重新创建实例,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 时,请注意以下几点:

  • 先调用 ongoingActivity.apply(context),然后再调用 notificationManager.notify(...)
  • 请务必为持续性活动设置静态图标,显式设置或设置为通过通知使用的后备图标均可。如果不进行此设置,您会收到 IllegalArgumentException

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

  • 请务必为持续性活动设置轻触 intent,显式设置或设置为使用通知的后备 intent 均可。如果不进行此设置,您会收到 IllegalArgumentException

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

  • 如果应用在清单中声明了多个 MAIN LAUNCHER 活动,请发布一个动态快捷方式,并使用 LocusId 将其与持续性活动相关联。

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

如果媒体内容正在 Wear OS 设备上播放,请发布媒体通知。这样,系统就可以创建相应的持续性活动。

如果您使用的是 Media3,系统会自动发布通知。如果您 手动创建通知时,应使用 MediaStyleNotificationHelper.MediaStyle、 而相应的 MediaSession 应包含其 会话活动 填充。