持续性活动

Wear OS 设备通常用于长时间运行的体验,例如跟踪锻炼情况。这会带来用户体验方面的挑战:如果用户开始执行某项任务,然后导航到表盘,那么如何返回到该任务?使用启动器返回应用可能很困难,尤其是在移动时,这会造成不必要的摩擦。

解决方案是将持续性通知与 OngoingActivity 配对。这样一来,设备便可在整个界面中显示有关长期活动的信息,从而实现表盘底部的可点按图标等功能。这样一来,用户可以了解后台任务,并能通过点按一下返回到应用。

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

跑步图标

图 1. 活动指示器。

持续性通知还会在全局应用启动器的“最近用过”部分显示信息。这样一来,用户便可在另一个便捷的位置查看任务状态并重新与应用互动:

启动器

图 2. 全局启动器。

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

计时器

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

map

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

音乐

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

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

请参阅“持续性活动”Codelab,通过详尽的示例了解如何为其他类型的应用创建持续性活动。

设置

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

dependencies {
  implementation "androidx.wear:wear-ongoing:1.1.0"
  implementation "androidx.core:core:1.17.0"
}

创建持续性 activity

此过程包括三个步骤:

  1. 创建标准 NotificationCompat.Builder 并将其配置为持续性。
  2. 创建并配置 OngoingActivity 对象,并将通知构建器传递给该对象。
  3. 将持续性活动应用于通知构建器,并发布生成的通知。

创建和配置通知

首先,创建一个 NotificationCompat.Builder。关键步骤是调用 setOngoing(true) 将其标记为持续性通知。您还可以在此阶段设置其他通知属性,例如小图标和类别。

// Create a PendingIntent to pass to the notification builder
val pendingIntent =
    PendingIntent.getActivity(
        this,
        0,
        Intent(this, AlwaysOnActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
        },
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
    )

val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
    .setContentTitle("Always On Service")
    .setContentText("Service is running in background")
    .setSmallIcon(R.drawable.animated_walk)
    // Category helps the system prioritize the ongoing activity
    .setCategory(NotificationCompat.CATEGORY_WORKOUT)
    .setContentIntent(pendingIntent)
    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
    .setOngoing(true) // Important!

创建 OngoingActivity

接下来,使用构建器创建 OngoingActivity 的实例。OngoingActivity.Builder 需要 Context(通知 ID)和您在上一步中创建的 NotificationCompat.Builder

配置将显示在新界面上的关键房源属性:

  • 动画图标和静态图标:提供在活动模式和氛围模式下显示在表盘上的图标。
  • 触摸 intent:一种 PendingIntent,用于在用户点按持续性活动图标时将用户带回您的应用。您可以重复使用上一步中创建的 pendingIndent

val ongoingActivity =
    OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
        // Sets the icon that appears on the watch face in active mode.
        .setAnimatedIcon(R.drawable.animated_walk)
        // Sets the icon that appears on the watch face in ambient mode.
        .setStaticIcon(R.drawable.ic_walk)
        // Sets the tap target to bring the user back to the app.
        .setTouchIntent(pendingIntent)
        .build()

应用于通知并发布

最后一步是将 OngoingActivity 与通知相关联,然后发布通知。ongoingActivity.apply() 方法会修改原始通知构建器,添加必要的数据,以便系统可以在额外的界面上显示通知。应用后,您可以像往常一样构建并发布通知。

// This call modifies notificationBuilder to include the ongoing activity data.
ongoingActivity.apply(applicationContext)

// Post the notification.
startForeground(NOTIFICATION_ID, notificationBuilder.build())

向启动器添加动态状态文本

上述代码会将可点按的图标添加到表盘。为了在启动器的最近部分中提供更丰富的实时更新,请创建 Status 对象并将其附加到 OngoingActivity。如果您未提供自定义 Status,系统会默认使用通知的内容文本(使用 setContentText() 设置)。

如需显示动态文字,请使用 Status.Builder。您可以定义包含占位符的模板字符串,并提供 Status.Part 对象来填充这些占位符。Status.Part 可以是动态的,例如秒表或计时器。

以下示例展示了如何创建显示“跑步 [秒表计时器]”的状态:

// Define a template with placeholders for the activity type and the timer.
val statusTemplate = "#type# for #time#"

// Set the start time for a stopwatch.
// Use SystemClock.elapsedRealtime() for time-based parts.
val runStartTime = SystemClock.elapsedRealtime()

val ongoingActivityStatus = Status.Builder()
    // Sets the template string.
    .addTemplate(statusTemplate)
    // Fills the #type# placeholder with a static text part.
    .addPart("type", Status.TextPart("Run"))
    // Fills the #time# placeholder with a stopwatch part.
    .addPart("time", Status.StopwatchPart(runStartTime))
    .build()

最后,通过在 OngoingActivity.Builder 上调用 setStatus(),将此 Status 关联到您的 OngoingActivity

val ongoingActivity =
    OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
        // ...
        // Add the status to the OngoingActivity.
        .setStatus(ongoingActivityStatus)
        .build()

其他自定义

除了 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,启动器会隐藏“最近用过”部分中来自相应软件包的所有应用项,只显示持续性 activity。

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

更新持续性活动

在大多数情况下,当开发者需要更新屏幕上的数据时,他们会创建一个新的持续性通知和一个新的持续性活动。不过,如果您想要保留实例而不是重新创建实例,Ongoing Activity API 还提供了用于更新 OngoingActivity 的辅助方法。

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

如需更新持续性活动和已发布的通知,请使用您之前创建的对象并调用 update(),如以下示例所示:

ongoingActivity.update(context, newStatus)

为方便起见,您可以采用一种静态方法来创建持续性活动。

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

停止持续性活动

当应用作为持续性活动完成运行时,只需取消持续性通知即可。

您还可以选择在应用进入前台时取消通知或持续性活动,然后在应用返回后台时重新创建它们,但这并不是必需的。

暂停持续性活动

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

最佳做法

使用 Ongoing Activity API 时,请注意以下几点:

  • 请务必为持续性活动设置静态图标,显式设置或设置为使用通知的回退机制均可。如果不进行此设置,您会收到 IllegalArgumentException

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

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

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

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

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

如果您使用的是 Media3,系统会自动发布通知。如果您手动创建通知,则应使用 MediaStyleNotificationHelper.MediaStyle,并且相应的 MediaSession 应填充其会话 activity