持续性活动

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

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

举个例子,某 Wear OS 用户可能使用锻炼应用在某个活动中记录跑步,然后离开该应用并启动一些其他任务。当用户离开锻炼应用时,该应用通常会转换为与某些后台工作(例如服务或闹钟管理器)相关联的持续性通知,让用户知道跑步的情况。通过该通知,用户就能始终掌握最新情况,并能轻松通过点按返回到应用中。

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

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

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

跑步图标

图 1. 活动指示器

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

启动器

图 2. 全局启动器

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

计时器

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

地图

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

音乐

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

Wear 会自动为媒体应用创建持续性活动。请参阅 GitHub 上的“持续性活动”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 本身作为通信机制。

持续性活动

有了持续性通知后,开始持续性活动非常简单。

以下代码示例包含可帮助您了解每个属性含义的注释:

Kotlin

var builder = 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)
        // In our case, 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 builder = 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)
        // In our case, 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 在新界面(例如启动器的“Recents”部分)上向用户显示 OngoingActivity 的当前实时状态。如需使用此功能,请使用 Status.Builder

大多数情况下,开发者只需要添加一个模板,用来表示要在应用启动器的“Recents”部分显示的文本。

开发者可以通过 Span 自定义文本外观:使用 addTemplate() 方法并将文本的任意动态部分指定为 Status.Part

以下示例展示了如何使单词“time”显示为红色。该示例使用 Status.TimerPartStatus.StopwatchPart 来表示应用启动器“Recents”部分的计时器或秒表。

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:纯文本或精密计时器。显示于应用启动器的“Recents”部分。如果未提供,系统会使用通知的“上下文文本”。

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

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

  • 持续性活动 ID:当应用有多个持续性活动时,用于消除关于 fromExistingOngoingActivityfromExistingOngoingActivity() 调用的歧义的 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 时,请注意以下几点:

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

  • 图标应为透明背景的黑白矢量图标。

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

  • 对于 NotificationCompat,请使用 core androidx 库 core:1.5.0-alpha05+,该库包含 LocusIdCompat新类别(锻炼、秒表或位置信息分享)。

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