正在进行的活动

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

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

例如,Wear OS 用户可以使用锻炼应用从活动中记录跑步,然后导航到其他应用以开始其他任务。 当用户从锻炼应用中导航时,该应用会过渡到与某些后台工作绑定的正在进行的通知,以使用户了解他们的跑步情况。 通知为用户提供更新,并提供了一种轻松的方法来点击回到应用。

但是,要查看通知,用户必须滑动到表盘下方的通知栏并找到正确的通知。 这不像其他界面那么方便。

借助正在进行的活动 API,应用的正在进行的通知可以将信息暴露给 Wear OS 上多个新的方便界面,以保持用户参与。

例如,在此锻炼应用中,信息可以作为可点击的跑步图标显示在用户的表盘上

running-icon

图 1. 活动指示器。

全局应用启动器的“最近”部分还会列出所有正在进行的活动

launcher

图 2. 全局启动器。

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

timer

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

map

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

music

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

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

有关为其他类型的应用创建正在进行的活动的深入示例,请参阅 正在进行的活动代码实验室

设置

要在您的应用中开始使用正在进行的活动 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, 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 在新的界面(例如启动器的“最近”部分)上向用户公开 OngoingActivity 的当前实时状态。要使用此功能,请使用 Status.Builder 子类。

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

然后,您可以使用 跨度 自定义文本的显示方式,方法是使用 addTemplate() 方法并将文本的任何动态部分指定为 Status.Part

以下示例演示如何使“时间”一词以红色显示。此示例使用 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。在应用程序启动器的“最近”部分中显示。如果未提供,则使用通知的“上下文文本”。

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

  • LocusId分配给持续进行的活动对应的启动器快捷方式的 ID。在活动持续进行期间,在启动器的“最近”部分中显示。如果未提供,则启动器会隐藏来自同一包的所有应用程序项目,并且仅显示持续进行的活动。

  • 持续进行的活动 ID:用于区分 fromExistingOngoingActivity() 的调用,前提是应用程序具有多个持续进行的活动。

更新持续进行的活动

在大多数情况下,开发人员在需要更新屏幕上的数据时会创建一个新的持续进行的通知和一个新的持续进行的活动。但是,持续进行的活动 API 还提供帮助程序方法来更新 OngoingActivity,如果您想要保留实例而不是重新创建它,则可以使用这些方法。

如果应用程序在后台运行,它可以向持续进行的活动 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);

停止持续进行的活动

当应用程序不再作为持续进行的活动运行时,它只需要取消持续进行的通知。

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

暂停持续进行的活动

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

最佳实践

使用持续进行的活动 API 时,请记住以下几点:

  • 在调用 notificationManager.notify(...) 之前,请调用 ongoingActivity.apply(context)
  • 为您的持续进行的活动设置静态图标,方法是 明确设置 或通过 通知 作为备用。如果您没有设置,您将收到 IllegalArgumentException

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

  • 为您的持续进行的活动设置触摸意图,方法是 明确设置 或使用 通知 作为备用。如果您没有设置,您将收到 IllegalArgumentException

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

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

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

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

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