正在进行的活动

在 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 以将它们绑定在一起。

状态

您使用 StatusOngoingActivity 的当前实时状态公开给用户,例如启动器的“最近使用”部分。要使用此功能,请使用 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() 的调用的 ID。

更新正在进行的活动

在大多数情况下,开发人员在需要更新屏幕上的数据时会创建一个新的正在进行的通知和一个新的正在进行的活动。但是,如果您希望保留实例而不是重新创建实例,则正在进行的活动 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 应填充其 会话活动