3. Wear OS应用架构与生命周期

Wear OS 上的健康应用很少只由一个界面构成。用户抬手看表时可能只看到表盘上的一个心率数字,点进去才进入完整测量界面;后台又要持续采集传感器数据,并在异常时发出提醒。这些体验分别由不同组件承担:Activity 负责前台交互,Service(尤其是前台服务)负责持续采集,Complication 把关键指标嵌入表盘,Tile 提供一瞥即得的快捷入口。理解它们的职责边界与交互方式,是设计健康应用架构的第一步。

四类组件的职责定位

Activity 是用户可见、可交互的界面载体。在健康场景中,它通常承载“测量心率”“查看今日步数曲线”“开始一次跑步记录”这类需要用户专注操作的任务。Activity 的生命周期受用户和系统共同支配:进入前台时 onResume,被表盘遮挡或息屏时 onPause,系统内存紧张时可能被销毁。因此 Activity 不适合承担长时间采集,一旦用户离开界面,采集逻辑就可能被中断。

Service 用于在后台执行不依赖界面的工作。健康应用中最常用的是前台服务(Foreground Service),它必须显示一条常驻通知,系统才允许其长期运行。持续心率监测、运动轨迹记录、睡眠阶段推断都适合放在前台服务中。它不随 Activity 销毁而停止,从而保证数据连续性。

Complication 是表盘上的数据插槽。它本身不绘制复杂界面,而是向表盘提供一小块数据或短文本,例如当前心率、今日步数、血氧值。表盘按自己的样式渲染,应用只负责按系统请求更新数据。它的价值在于“零点击可见”。

Tile 是用户从表盘滑动进入的快捷面板,用少量控件展示信息并提供一两个操作入口,例如“开始测量”“查看最近一次心率”。它比 Activity 轻,比 Complication 承载的信息更多,适合作为高频操作的跳板。

组件之间的交互关系

这四类组件并非孤立,而是围绕同一份健康数据协同工作。典型链路是:前台服务持续采集并写入本地数据层(如 Room 数据库或 Data Layer);Complication 定期从数据层读取最新值推送给表盘;Tile 读取同一数据层展示摘要,并在用户点击时通过 Intent 或 RemoteActivityHelper 拉起 Activity;Activity 则负责详细展示与用户主动发起的测量。

Wear OS 健康应用组件协作架构 传感器 / 健康服务 心率 · 加速度 · PPG 前台服务 持续采集 · 常驻通知 本地数据层 Room / Data Layer Complication 表盘数据插槽 Tile 快捷面板 · 摘要 Activity 详细界面 · 主动测量 表盘 渲染 Complication 数据流 写入 读取 读取 点击拉起 推送 读写 生命周期要点 • Activity:随界面可见性启停 • 前台服务:独立于界面持续运行 • Complication:按表盘请求更新 • Tile:进入时刷新,退出即释放

图中可以看到,前台服务是数据的主要生产者,数据层是共享枢纽,Complication、Tile、Activity 都是数据的消费者或读写者。这种“单一数据源 + 多消费者”的结构,避免了各组件各自维护状态导致的数据不一致。

生命周期与数据一致性

健康数据对连续性敏感,因此要特别关注各组件的生命周期差异。Activity 被销毁时,正在进行的测量若只写在 Activity 内就会丢失;正确做法是把采集逻辑放在前台服务,Activity 只订阅数据并展示。Complication 的更新由系统调度,不能假设它实时刷新,因此它读取的应是数据层中已落库的最新值,而不是临时内存变量。Tile 在用户滑入时创建、滑出后可能被销毁,所以它不应持有长连接,而应在每次进入时重新读取数据层。

一个常见的坑是:在 Activity 中直接启动一个普通后台 Service 做持续采集。Android 8.0 以后后台服务受限,应用退到后台后服务很快会被停止,导致心率曲线出现断点。正确选择是使用 startForegroundService() 并在 5 秒内调用 startForeground() 显示通知。下面是一个前台服务启动采集的骨架示例:

class HeartRateService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = buildNotification()   // 常驻通知,含心率值
        startForeground(NOTIF_ID, notification)  // 必须在 5 秒内调用
        startCollecting()                        // 启动传感器采集协程
        return START_STICKY
    }

    private fun startCollecting() {
        scope.launch {
            sensorClient.heartRateFlow().collect { bpm ->
                repository.insert(HeartRateRecord(bpm, System.currentTimeMillis()))
                updateNotification(bpm)          // 同步刷新通知
            }
        }
    }
}

采集到的每条记录都写入 repository,Complication 和 Tile 后续从同一仓库读取,从而保证表盘、快捷面板和详情页看到的是同一份数据。

Complication 与 Tile 的更新时机

Complication 通过 ComplicationDataSourceService 响应表盘的更新请求。系统会在合适时机调用 onComplicationRequest,应用在其中返回当前值。健康场景中,心率这类高频变化的数据不宜每次变化都推送,否则耗电明显;更合理的策略是设定最小更新间隔,例如 60 秒,或在数值跨越阈值(如静息心率超过 100)时才主动请求刷新。

Tile 通过 TileService 的 onStartListening 与 onStopListening 管理生命周期。进入时读取数据层并构建布局,退出时释放监听。若 Tile 上放置“开始测量”按钮,点击后应通过 RemoteActivityHelper.startRemoteActivity() 拉起 Activity,而不是在 Tile 内直接执行长时间任务。

设计原则:让前台服务负责“持续产生数据”,让数据层负责“保存数据”,让 Complication、Tile、Activity 各自负责“以合适的形式呈现数据”。职责清晰后,生命周期差异带来的问题会大幅减少。

组件选择速查

需求首选组件原因
持续记录跑步心率前台服务不随界面销毁,可长期运行
表盘显示当前步数Complication零点击可见,由表盘渲染
快速开始一次测量Tile滑入即用,可拉起 Activity
查看历史曲线与详情Activity需要完整交互界面
异常心率提醒前台服务 + 通知后台判断并主动触达用户

把这张表与前面的架构图对照,可以快速判断一个新功能应该落在哪个组件上。核心判断依据是:它是否需要用户持续注视(Activity)、是否需要脱离界面长期运行(前台服务)、是否只需一瞥可见(Complication)、是否是高频操作的快捷入口(Tile)。四者通过共享数据层协作,共同构成完整的健康应用体验。