始终开启的应用与系统氛围模式

本指南介绍了如何使应用始终处于运行状态、如何对电源状态转换做出反应,以及如何管理应用行为以在节省电池电量的同时提供良好的用户体验。

让应用始终可见会严重影响电池续航时间,因此在添加这项功能时,请考虑对电量的影响。

关键概念

当 Wear OS 应用全屏显示时,它处于以下两种电源状态之一:

  • 互动:一种高功率状态,屏幕处于全亮度,允许用户进行全面互动。
  • 微光:一种低功耗状态,显示屏会调暗以节省电量。在此状态下,应用的界面仍会占据整个屏幕,但系统可能会通过模糊处理或显示叠加层内容(例如时间)来改变其外观。这也称为氛围模式

操作系统控制着这些状态之间的转换。

始终开启的应用是指在互动氛围状态下均显示内容的应用。

当一个始终开启的应用在设备处于低功耗环境状态时继续显示自己的界面时,该应用被描述为处于 ambiactive 模式

系统过渡和默认行为

当应用位于前台时,系统会根据用户非活跃状态触发的两个超时时间来管理电源状态转换。

  • 超时 #1:从互动状态到氛围状态:在用户处于非活动状态一段时间后,设备会进入氛围状态。
  • 超时 #2:返回到表盘:在闲置一段时间后,系统可能会隐藏当前应用并显示表盘。

系统首次过渡到环境状态后,默认行为取决于 Wear OS 版本和应用的配置:

  • 在 Wear OS 5 及更低版本中,系统会显示已暂停应用的模糊屏幕截图,时间叠加在顶部。此状态在以下流程图中由“AOD Lite”节点表示。
  • 在 Wear OS 6 及更高版本中,如果应用以 SDK 36 或更高版本为目标平台,则会被视为始终处于开启状态。显示屏变暗,但应用继续运行并保持可见。(更新频率可能低至每分钟一次。)此状态在以下流程图中由“全局 AOD”节点表示。

自定义微光状态的行为

无论默认系统行为如何,在所有 Wear OS 版本上,您都可以使用 LocalAmbientModeManager 观察 Wear OS 上 Jetpack Compose 中的状态转换,从而在应用处于环境状态时自定义其外观或行为。此状态在以下流程图中由“Ambiactive 模式”节点表示。

观察环境状态

如需在 Wear OS 上的 Jetpack Compose 中对环境模式事件做出响应,请执行以下操作:

  1. 使用 LocalAmbientModeManager 观察系统环境模式状态的变化,并相应地调整可组合项。配置组件,使其在 AmbientMode.Ambient 状态下调整为低功耗模式,并在 AmbientMode.Interactive 状态下恢复为完全互动式显示。

    // In a production application, the AmbientModeManager should be instantiated and provided at
    // the highest level of the Compose hierarchy (typically in the host Activity's setContent
    // block) using a CompositionLocalProvider. This ensures proper lifecycle management and
    // broad accessibility.
    
    // For this self-contained demo, AmbientModeManager is created and provided locally:
    val activityAmbientModeManager = rememberAmbientModeManager()
    CompositionLocalProvider(LocalAmbientModeManager provides activityAmbientModeManager) {
        val ambientModeManager = LocalAmbientModeManager.current
        val ambientMode = ambientModeManager?.currentAmbientMode
    
        if (ambientModeManager != null) {
            ambientModeManager.AmbientTickEffect {
                // While device is in ambient mode, update properties every minute or so
                // ...
            }
        }
    
        // ...
            val ambientModeName =
                when (ambientMode) {
                    is AmbientMode.Interactive -> "Interactive"
                    is AmbientMode.Ambient -> "Ambient"
                    else -> "Unknown"
                }
    
            Text(text = "$ambientModeName Mode")
            // ...
    }

环境行为流程图

以下流程图说明了系统如何根据设备的 Wear OS 版本、应用的 targetSdkVersion 以及应用是否观察到环境状态转换(使用 LocalAmbientModeManagerAmbientLifecycleCallback)来确定环境行为。

一个流程图,用于说明 Wear OS 环境模式的决策逻辑。它展示了设备的操作系统版本和应用的配置如何决定三种结果之一:模糊叠加层、全局 AOD 或应用管理的 Ambiactive 模式。
图 1:一个流程图,用于说明 Wear OS 微光模式的决策逻辑。

控制屏幕开启时长

以下部分介绍了如何管理应用在屏幕上停留的时间。

防止返回到显示持续性活动或实时更新的表盘

环境状态(超时 #2)持续一段时间后,系统通常会返回到表盘。用户可以在系统设置中配置超时时长。对于某些使用情形(例如用户跟踪锻炼情况),应用可能需要保持更长时间的可见性。

在 Wear OS 5 及更高版本中,您可以通过实现持续性活动来防止这种情况。如果您的应用正在显示有关正在进行的用户任务(例如锻炼会话)的信息,您可以使用 Ongoing Activity API 来保持应用可见,直到任务结束。如果用户手动返回到表盘,持续性活动指示器可让用户通过一次点按返回到您的应用。

或者,在 Wear OS 7 及更高版本中,您可以使用实时更新来代替持续性 activity。为了实现向后兼容性,继续在搭载 Wear OS 6 或更低版本的设备上支持持续进行的 activity。

为此,持续性通知的触摸 intent 必须指向始终开启的 activity,如以下代码段所示:

val activityIntent =
    Intent(this, AlwaysOnActivity::class.java).apply {
        flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
    }

val pendingIntent =
    PendingIntent.getActivity(
        this,
        0,
        activityIntent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
    )

val notificationBuilder =
    NotificationCompat.Builder(this, CHANNEL_ID)
        // ...
        // ...
        .setOngoing(true)

// ...

val ongoingActivity =
    OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
        // ...
        // ...
        .setTouchIntent(pendingIntent)
        .build()

ongoingActivity.apply(applicationContext)

val notification = notificationBuilder.build()

保持屏幕开启并防止进入氛围状态

在极少数情况下,您可能需要完全阻止设备进入氛围状态。也就是说,避免出现超时 #1。为此,您可以使用 keepScreenOn 修饰符。此函数用作唤醒锁定,使设备保持在 Interactive 状态。请谨慎使用此功能,因为它会严重影响电池续航时间。

有关微光模式的建议

为了在环境模式下提供最佳用户体验并节省电量,请遵循以下设计指南。这些建议优先考虑清晰的用户体验,通过防止误导性信息和减少视觉杂乱来优化显示功耗。

  • 减少视觉杂乱感并降低显示屏功耗。简洁的极简界面可向用户表明应用处于低能耗状态,并通过限制高亮像素来节省大量电池电量。
    • 使至少 85% 的屏幕为黑色(亮像素比率为 15% 或更低)。
    • 仅显示最关键的信息,并将次要细节移至互动显示屏。
    • 对于大图标或按钮,请使用轮廓而不是实心填充,并调整轮廓对比度,使元素保持清晰可辨,同时避免屏幕闪烁或烙印。
    • 避免使用大块纯色和非功能性品牌宣传或背景图片。
  • 处理过时的动态数据和动画
    • 为了节省电量,系统仅会定期(通常每分钟一次)调用 AmbientTickEffect 回调。由于此限制,任何频繁变化的数据(例如秒表、心率或锻炼距离)在更新之间都会过时。为避免显示误导性信息和不正确的信息,请将这些实时值替换为静态占位内容,例如 --
    • 移除需要频繁更新才能对用户当前情境或情况有用的进度指示器,例如短媒体会话的进度环。对于每分钟更新一次仍有价值的情况(例如长定时器),您可以保留进度指示器。
    • 在环境模式下暂停或移除所有动画,包括动画进度指示器和滚动跑马灯文字。
  • 保持一致的布局
    • 互动氛围模式下,让元素保持在同一位置,以实现平滑过渡。由于新版 Wear OS 设备在环境会话期间支持触控输入,因此按钮等互动式界面元素可以保持可见。
    • 始终显示当前时间。在 Jetpack Compose for Wear OS 中,TimeText(默认包含在 ScreenScaffold 中)可感知环境光,并且在环境光模式下每分钟自动更新一次。
  • 感知上下文
    • 如果用户在设备进入环境模式时位于设置或配置界面,请考虑显示应用中更相关的界面,而不是设置视图。
  • 处理设备专用要求
    • currentAmbientMode 返回 AmbientMode.Ambient 时:
      • 如果 isLowBitAmbientSupportedtrue,则表示显示屏在微光模式下支持的每种颜色位数较少。停用抗锯齿功能(例如,在自定义 CanvasPaint 绘制操作中),并尽可能避免使用颜色渐变。
      • 如果 isBurnInProtectionRequiredtrue,系统会定期移动显示内容,以防止像素烙印。请勿手动移动应用中的界面元素。相反,请避免使用纯白色区域,并将关键界面元素放置在距离屏幕边缘至少 10 像素的位置,这样当系统移动显示内容时,这些元素就不会被剪裁。(标准 Wear Compose 布局(例如 ScreenScaffold)已提供足够的边缘内边距。)

调试和测试

在开发或测试应用在设备处于环境模式时的行为时,这些 adb 命令可能会很有用:

# put device in ambient mode if the always on display is enabled in settings
# (and not disabled by other settings, such as theatre mode)
$ adb shell input keyevent KEYCODE_SLEEP

# put device in interactive mode
$ adb shell input keyevent KEYCODE_WAKEUP

示例:锻炼应用

假设有一个锻炼应用需要在整个锻炼时段内向用户显示指标。应用必须在整个环境状态转换过程中保持可见,并避免被表盘替换。

为实现此目的,开发者应执行以下操作:

  1. 使用 LocalAmbientModeManager 处理 InteractiveAmbient 状态之间的界面变化,例如调暗屏幕和移除非必要数据。
  2. 为遵循最佳实践的环境状态创建新的低功耗布局
  3. 在锻炼期间使用 Ongoing Activity API(或 Wear OS 7 及更高版本中的实时更新),以防止系统返回到表盘。

如需查看完整实现,请参阅 GitHub 上基于 Compose 的运动示例。此示例还演示了如何使用 LocalAmbientModeManager 可组合函数来简化 Compose 中的环境音模式处理。