HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(十八):【手表协同】烹饪计时器流转至智能手表——手腕掌控烹饪节奏

HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(十八):【手表协同】烹饪计时器流转至智能手表------手腕掌控烹饪节奏

摘要 :上一篇我们为菜谱详情页装上了"耳朵"------接入 CoreSpeechKit 语音识别实现了免提声控启动播报。但烹饪中还有一个高频痛点:计时 。炖牛肉要 40 分钟,蒸鱼要 8 分钟,你不可能一直盯着手机。如果计时器能流转到手表上------手腕一抬就看到倒计时,到点了轻震提醒------那才是真正的"云端厨房"。本篇,我们将接入 HarmonyOS 6.1.0(API 23)的分布式设备协同 能力,为《灵犀厨房》的厨电控制页实现计时器从手机流转至智能手表的全链路:手机端一键发送→手表端接管倒计时→倒计时最后 10 秒渐强震动→计时完成重复震动提醒。严格遵循 API 23 规范,代码即文档。


一、引言与系列定位

经过第 13 篇的厨电模拟和第 17 篇的声控播报,你的《灵犀厨房》已经成为一台强大的烹饪助手。但你很快会发现一个尴尬的现实:

场景 问题 解决
电磁炉定时 15 分钟煮汤 离开厨房就看不到倒计时 流转至手表,手腕上实时显示
烤箱烘烤 40 分钟 在客厅看电视,忘记还剩多久 抬手看手表,剩余时间一目了然
计时器响了但手机在隔壁 铃声听不到,菜烧糊了 手表震动提醒,戴在手上不会错过
倒计时最后 10 秒 需要紧张地等待关火 手表渐强震动,倒计时节奏可感知

设计决策:为何不在手表上操作计时器的"开始/暂停/停止"?

经过对烹饪场景的分析,我们发现:在厨房里操作手机已经满手油污,操作手表的小屏幕同样不便。真正的痛点不是"用哪只手操作",而是"不用手操作"。因此,本篇的设计原则是:

  • 手机端发起计时:在厨电控制页点击"定时 5 分钟"或"定时 15 分钟"(第 13 篇已实现),计时器在手机上运行
  • 一键流转至手表:计时器运行后,点击"⌚ 流转至手表",手机端计时停止,手表接管倒计时
  • 手表专职提醒:倒计时最后 10 秒渐强震动 → 计时完成重复震动 → 手腕一抬就知道该关火了
  • 手机端回看状态:手机端页面顶部显示"电磁炉 计时中 · HUAWEI WATCH GT 4 · 剩余 03:15"

这就是手表协同的核心价值------从"盯着屏幕"变为"手腕感知"。不是用手表控制厨电,而是让计时跟着你的手腕走。


二、核心原理与底层机制深度解读

2.1 HarmonyOS 分布式手表通信:三条技术路径

HarmonyOS 6.1.0(API 23)为手机与手表的协同提供了三条技术路径:
⌚ 手表端
📱 手机端
选择路径
通信路径
路径 A

@kit.WearEngine

wearEngine.sendMessage()
路径 B

@kit.DistributedServiceKit

continuationManager
路径 C

自定义蓝牙通道

(低功耗)
灵犀厨房 App
WatchTimerManager
WatchServiceHelper
手表 App/服务
倒计时引擎
震动反馈

路径 适用场景 优势 劣势 本篇选型
A: WearEngine 手表专属消息通道 延迟低(~50ms),支持后台保活 仅限手表设备 模拟实现
B: Continuation 跨设备任务迁移 通用性强,支持任意设备 延迟较高(~200ms),需用户确认 备选
C: 自定义蓝牙 极端省电场景 功耗最低 开发成本高,需自行维护连接 不考虑

为什么选 WearEngine 模拟实现?

@kit.WearEngine 是 HarmonyOS 专门为手表协同设计的 Kit,提供了消息收发、设备状态查询、震动控制等 API。在不具备真实手表开发环境的情况下,我们采用与第 14 篇分布式流转一致的策略:用模拟代码完整保留 API 23 的真实接口签名,后续接入真实手表仅需替换服务层实现,上层 UI 零改动

2.2 计时器流转的状态机设计

这是本篇最核心的设计------手机与手表之间的计时器流转不是一个简单的数据拷贝,而是一个涉及设备间状态同步的分布式状态机:
无计时器
用户点击"定时5分钟"
点击"⌚流转至手表"
手表确认接收
流转失败→回退
手表端倒计时结束
用户取消手表计时
用户清除完成状态
IDLE
COUNTING_ON_PHONE
TRANSFERRING_TO_WATCH
COUNTING_ON_WATCH
COMPLETED_ON_WATCH
手机端 setInterval 每秒递减
手表端接管倒计时

手机端通过回调显示进度
手表震动已触发

手机端显示"计时完成"

核心机制 :流转不是"同步两份数据",而是单源倒计时 + 状态上报。计时器在任一时刻只在一个设备上运行(手机或手表),另一端通过回调被动接收状态更新。这避免了双端倒计时不一致的问题。

2.3 手表震动反馈的三段式设计

倒计时从"数字"变成"触觉",需要分阶段提醒:

阶段 触发条件 震动模式 用户感知
安静期 剩余 > 10 秒 无震动 手腕无感,正常看倒计时数字
预警期 剩余 10 秒 RAMP_UP 渐强震动(500ms) "快好了,准备好关火"
完成期 剩余 0 秒 REPEAT 重复震动(3 秒) "到了!马上去关火!"

这就是震动反馈的三段式设计:安静 → 预警 → 告警,让用户不依赖屏幕就能感知计时节奏。


三、架构设计 / 核心逻辑图解

3.1 手表协同的四层架构

🏗️ 基础设施层
⚙️ 服务能力层
🧠 业务功能层
🎨 用户交互层
KitchenDevicePage

⌚ 手表状态指示条

设备卡片中的流转按钮

计时完成通知
WatchTimerManager

流转状态机

手表回调桥接

transferToWatch / cancelFromWatch
KitchenDeviceSimulator

(第13篇已实现)

计时器引擎
WatchServiceHelper

手表设备发现

计时器数据传输

震动控制

倒计时模拟
@kit.WearEngine

(模拟)

消息收发 + 设备管理
@kit.DistributedServiceKit

(可选)

continuationManager

设计原则 :手表协同(WatchTimerManager)与计时器引擎(KitchenDeviceSimulator)是独立的 Business 层模块。WatchTimerManager 的职责是"协调流转 + 状态上报 + 回调通知 UI",由 UI 层(KitchenDevicePage)的 handleWatchTransfer 方法来实际触发停止手机端计时和发起手表流转。这样:

  1. 单一职责:WatchTimerManager 只管"何时流转、流转到哪、流转状态",UI 层决定"流转前停止哪些手机端计时器、流转后显示什么状态"。
  2. 双端解耦transferToWatch 时不依赖 KitchenDeviceSimulator 的内部状态------它只接收 remainingSeconds(剩余秒数)作为参数,手表端独立倒计时,与手机端彻底隔离。
  3. 震动独立控制 :手表震动逻辑在 WatchServiceHelper 层,不污染业务层。

3.2 流转时序图

⌚ 手表 WatchServiceHelper WatchTimerManager KitchenDeviceSimulator KitchenDeviceViewModel KitchenDevicePage 👤 用户 ⌚ 手表 WatchServiceHelper WatchTimerManager KitchenDeviceSimulator KitchenDeviceViewModel KitchenDevicePage 👤 用户 用户在厨电控制页 手机端倒计时开始 手机端计时器停止(保留剩余秒数) 顶部显示"电磁炉 计时中 · HUAWEI WATCH GT 4" 更新手表倒计时显示 loop [每秒] 顶部显示"电磁炉 计时完成!" 点击"定时 5分钟" startTimer(deviceId, 300) startCookingTimer(deviceId, 300) 手机端显示 04:59, 04:58... 点击"⌚ 流转至手表" stopTimer(deviceId) transferToWatch(deviceId, deviceName, remaining, total) 状态 → TRANSFERRING_TO_WATCH transferTimer(context) 模拟蓝牙传输 (~250ms) 发送计时数据 手表端启动倒计时 流转成功 状态 → COUNTING_ON_WATCH onTransferred(context) mockWatchRemaining-- onWatchTimerStateChange(COUNTING, remaining) onWatchTick(remainingSeconds, totalSeconds) 最后10秒 → 渐强震动 remaining=0 重复震动 3秒 状态 → COMPLETED onWatchComplete()

关键时序:手机端停止计时 → 再发起手表流转 → 手表端启动倒计时。这个顺序很重要------如果先流转再停止,可能出现双端同时计时的竞态。


四、实战:为厨电控制页装上"手腕大脑"

Step 1:WatchServiceHelper ------ 封装手表通信

新建 services/WatchServiceHelper.ets。核心任务:将手表的设备发现、消息传输、震动控制封装为可复用的服务层,完整保留 API 23 的真实接口签名。

typescript 复制代码
// services/WatchServiceHelper.ets
// 所属层:服务能力层 (Service Layer)
// 职责:封装 @kit.WearEngine 的手表通信能力
// 版本:v1 --- 第18篇,基于 API 23

// ========== 真实手表 API(上架后取消注释) ==========
// import { wearEngine } from '@kit.WearEngine';
// import { distributedDeviceManager } from '@kit.DistributedServiceKit';

/** 手表设备信息 */
export interface WatchDevice {
  deviceId: string;      // 设备唯一 ID
  name: string;          // 'HUAWEI WATCH GT 4'
  model: string;         // 'PNX-B19'
  status: WatchConnectionStatus;
  osVersion: string;
  batteryLevel: number;  // 0-100
  isWorn: boolean;       // 是否穿戴中
}

/** 计时器流转上下文:手机→手表传输的计时数据 */
export interface WatchTimerContext {
  recipeName: string;
  deviceName: string;        // 厨电名称,如"电磁炉"
  remainingSeconds: number;   // 剩余秒数
  totalSeconds: number;       // 总秒数
  timestamp: number;
  vibrationPattern?: WatchVibrationPattern;
}

/** 手表震动模式 */
export enum WatchVibrationPattern {
  SHORT = 'short',      // 单次短震
  REPEAT = 'repeat',    // 重复短震(计时到点)
  RAMP_UP = 'rampUp'    // 渐强震动(最后10秒)
}

/** 手表端计时器状态 */
export enum WatchTimerState {
  IDLE = 'idle', RECEIVING = 'receiving',
  COUNTING = 'counting', COMPLETED = 'completed', CANCELLED = 'cancelled'
}

export class WatchServiceHelper {
  private watchDevice: WatchDevice | null = null;
  private timerState: WatchTimerState = WatchTimerState.IDLE;
  private mockWatchRemaining: number = 0;

  // ★ 初始化:扫描并连接手表
  async initialize(): Promise<WatchDevice | null> {
    await this.delay(400);  // 模拟蓝牙扫描延迟
    this.watchDevice = {
      deviceId: 'WA:TC:H0:00:00:01',
      name: 'HUAWEI WATCH GT 4',
      model: 'PNX-B19',
      status: WatchConnectionStatus.CONNECTED,
      osVersion: 'HarmonyOS 4.2.0',
      batteryLevel: 85,
      isWorn: true
    };
    return this.watchDevice;
  }

  /** ★ 检查手表是否可用(已连接 + 穿戴中) */
  isWatchAvailable(): boolean {
    return this.watchDevice !== null
      && this.watchDevice.status === WatchConnectionStatus.CONNECTED
      && this.watchDevice.isWorn;
  }

  /** ★ 将计时器流转至手表 */
  async transferTimer(context: WatchTimerContext): Promise<boolean> {
    if (!this.isWatchAvailable()) return false;
    // 模拟数据传输延迟
    await this.delay(250);
    // 启动模拟手表端倒计时
    this.startMockWatchCountdown(context.remainingSeconds);
    return true;
  }

  /** ★ 启动模拟手表端倒计时(每秒递减 + 震动逻辑) */
  private startMockWatchCountdown(seconds: number): void {
    this.mockWatchRemaining = seconds;
    this.mockWatchInterval = setInterval(() => {
      if (this.mockWatchRemaining <= 0) {
        // 计时完成 → 触发手表震动
        this.triggerVibration(WatchVibrationPattern.REPEAT, 3000);
        this.notifyTimerCompleted();
        return;
      }
      this.mockWatchRemaining--;
      // 最后 10 秒 → 渐强震动提醒
      if (this.mockWatchRemaining === 10) {
        this.triggerVibration(WatchVibrationPattern.RAMP_UP, 500);
      }
      this.notifyWatchTimerStateChange(WatchTimerState.COUNTING, this.mockWatchRemaining);
    }, 1000);
  }
}

export const watchServiceHelper: WatchServiceHelper = new WatchServiceHelper();

核心点解读

  • isWorn 检查 :如果手表摘下放在桌上(isWorn=false),震动提醒将失效------流转前必须确认手表佩戴状态,避免"手表在桌上震,人在厨房听不到"。
  • 模拟倒计时的真实性setInterval 每秒触发一次递减,完全模拟真实手表端的计时节奏。后续接入真机时,倒计时逻辑移至手表 App 内执行,手机端通过 onMessage 回调被动接收进度。
  • 全局单例导出export const watchServiceHelper 确保整个应用共用一个手表连接实例。

Step 2:WatchTimerManager ------ 流转状态机 + 业务逻辑

新建 business/WatchTimerManager.ets。核心任务:(1)定义 6 种流转状态;(2)实现 transferToWatch 核心方法,协调"停止手机计时→发起手表流转"的全流程;(3)桥接 Service 层回调到 Business 层语义。

typescript 复制代码
// business/WatchTimerManager.ets
// 所属层:业务功能层 (Business Layer)
// 职责:管理计时器在手机与手表之间的流转状态

/** 手表计时器管理器状态枚举 */
export enum TimerTransferState {
  IDLE = 'idle',                     // 无计时器运行
  COUNTING_ON_PHONE = 'countingOnPhone',   // 仅在手机端
  TRANSFERRING_TO_WATCH = 'transferringToWatch', // 流转中
  COUNTING_ON_WATCH = 'countingOnWatch',     // 在手表端
  COMPLETED_ON_WATCH = 'completedOnWatch',   // 手表完成
  CANCELLED = 'cancelled'
}

export class WatchTimerManager {
  private state: TimerTransferState = TimerTransferState.IDLE;
  private preservedSeconds: number = 0;   // 流转前保存的剩余秒数
  private totalSeconds: number = 0;

  /** ★ 核心方法:将计时器流转至手表 */
  async transferToWatch(
    deviceId: string, deviceName: string, recipeName: string,
    remainingSeconds: number, totalSeconds: number
  ): Promise<boolean> {
    if (!watchServiceHelper.isWatchAvailable()) {
      this.notifyError('手表未连接或未佩戴,请戴好手表后重试');
      return false;
    }
    // 保存上下文(用于从手表收回时恢复)
    this.preservedSeconds = remainingSeconds;
    this.totalSeconds = totalSeconds;
    this.setState(TimerTransferState.TRANSFERRING_TO_WATCH);

    const context: WatchTimerContext = {
      recipeName, deviceName, remainingSeconds, totalSeconds,
      timestamp: Date.now()
    };

    const success = await watchServiceHelper.transferTimer(context);
    if (success) {
      this.setState(TimerTransferState.COUNTING_ON_WATCH);
    } else {
      this.setState(TimerTransferState.COUNTING_ON_PHONE); // 回退
    }
    return success;
  }

  /** 从手表取消计时器 */
  async cancelFromWatch(resumeOnPhone: boolean = true): Promise<void> {
    await watchServiceHelper.cancelWatchTimer();
    if (resumeOnPhone && this.preservedSeconds > 0) {
      this.setState(TimerTransferState.COUNTING_ON_PHONE);
    } else {
      this.setState(TimerTransferState.IDLE);
    }
  }
}

export const watchTimerManager: WatchTimerManager = new WatchTimerManager();

核心点解读

  • 流转失败的状态回退 :如果 transferTimer 失败(如蓝牙中断),状态立即从 TRANSFERRING_TO_WATCH 回退到 COUNTING_ON_PHONE------用户不需要额外操作,手机端计时器继续运行。
  • preservedSeconds 预留恢复能力:保留流转前的剩余秒数,即使流转到手表后断连,也能在取消时回到手机端重新计时。
  • 状态变更全部通过 setState 做日志记录:每个状态迁移都有控制台日志,方便调试分布式场景下的时序问题。

Step 3:改造 KitchenDevicePage.ets,集成手表流转 UI

在厨电控制页中新增手表状态管理和流转逻辑。

(1)新增状态变量

typescript 复制代码
// ★ mock 场景下手表始终可用,初始 true 确保首帧即显示手表电量 badge
@Local isWatchConnected: boolean = true;
@Local watchDeviceName: string = 'HUAWEI WATCH GT 4';
@Local watchDeviceBattery: number = 85;
@Local transferState: TimerTransferState = TimerTransferState.IDLE;
@Local watchRemainingSeconds: number = 0;
@Local watchTimerDeviceName: string = '';

// ★ 记录各设备的原始计时总秒数(流转时传 totalSeconds,而非当前剩余秒数)
private deviceTimerTotal: Map<string, number> = new Map();

(2)生命周期集成

typescript 复制代码
async aboutToAppear(): Promise<void> {
  this.vm.loadDevices();
  await this.initWatchManager();  // ★ 初始化手表连接
}

aboutToDisappear(): void {
  this.vm.destroy();
  watchTimerManager.destroy();  // ★ 销毁手表连接
}

(3)手表初始化和回调注册

typescript 复制代码
private async initWatchManager(): Promise<void> {
  watchTimerManager.registerCallbacks({
    onStateChange: (state) => { this.transferState = state; },
    onWatchTick: (remaining, total) => {
      this.watchRemainingSeconds = remaining;
      this.watchTotalSeconds = total;
    },
    onWatchComplete: () => { /* 显示计时完成状态 */ },
    onWatchConnectionChange: (status, device) => {
      this.isWatchConnected = (status === WatchConnectionStatus.CONNECTED);
      if (device) {
        this.watchDeviceName = device.name;
        this.watchDeviceBattery = device.batteryLevel;
      }
    }
  });
  await watchTimerManager.initialize();
  const device = watchTimerManager.getWatchDevice();
  if (device) { /* this.isWatchConnected already true; only update name/battery */ }
}

(4)handleWatchTransfer ------ 发起流转的核心方法

typescript 复制代码
private async handleWatchTransfer(
  deviceId: string, deviceName: string,
  remainingSeconds: number, totalSeconds: number
): Promise<void> {
  console.info(`[KitchenDevicePage] 🔄 流转计时器: ${deviceName}, 剩余 ${remainingSeconds}秒`);

  this.watchTimerDeviceName = deviceName;
  this.watchRemainingSeconds = remainingSeconds;
  this.watchTotalSeconds = totalSeconds;

  // ★ 先停止手机端计时器
  this.vm.stopTimer(deviceId);

  // 执行手表流转
  const success = await watchTimerManager.transferToWatch(
    deviceId, deviceName, '', remainingSeconds, totalSeconds
  );

  if (!success) {
    console.error('[KitchenDevicePage] 流转至手表失败');
    this.watchTimerDeviceName = '';
  }
}

(5)UI 层:手表流转状态指示条

typescript 复制代码
// ★ 流转中 / 计时中状态
if (this.transferState === TimerTransferState.COUNTING_ON_WATCH) {
  Row() {
    Text('⌚').fontSize(18)
    Column() {
      Text(`${this.watchTimerDeviceName} 计时中`)
        .fontSize(13).fontColor('#FF6B35').fontWeight(FontWeight.Medium)
      Text(`${this.watchDeviceName} · 剩余 ${this.formatWatchTimer(this.watchRemainingSeconds)}`)
        .fontSize(11).fontColor('#999')
    }
    Blank()
    Button('取消手表计时').onClick(() => { /* ... */ })
  }
  .backgroundColor('#FFF3E0').borderRadius(10)
}

// ★ 计时完成状态
if (this.transferState === TimerTransferState.COMPLETED_ON_WATCH) {
  Row() {
    Text('⌚').fontSize(18)
    Text(`${this.watchTimerDeviceName} 计时完成!`)
      .fontSize(13).fontColor('#4CAF50')
    Button('清除')
  }
  .backgroundColor('#F0FFF0').borderRadius(10)
}

(6)DeviceControlCard 新增流转按钮

DeviceControlCard 中新增 @Event onWatchTransfer 并在计时区域渲染按钮:

typescript 复制代码
// ★ @Event 声明(与 onTimerStop 等并列)
@Event onWatchTransfer: () => void = () => {};

// build() 中计时区域新增按钮
if (this.device.timerSeconds > 0) {
  Row() {
    Text('⏱️').fontSize(18)
    Text(this.formatTimer(this.device.timerSeconds))
      .fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FF6B35').fontFamily('monospace')
      .margin({ left: 8 })
    Blank()
    // ★ "⌚ 流转至手表"按钮 --- onClick 直接传 @Event 引用
    Button() {
      Text('⌚ 流转至手表').fontSize(11).fontColor('#FF6B35')
    }
    .height(30).backgroundColor('#FFF0E6').borderRadius(8)
    .margin({ right: 6 })
    .onClick(this.onWatchTransfer)

    Button('停止计时')
      .fontSize(12).height(30).backgroundColor('#F44336').fontColor(Color.White).borderRadius(8)
      .onClick(() => this.onTimerStop?.())
  }
  .width('100%').padding(10).backgroundColor('#FFF3E0').borderRadius(8).margin({ bottom: 12 })
}

父组件 KitchenDevicePageForEach 中绑定回调,记录原始总秒数并通过 handleWatchTransfer 执行流转:

typescript 复制代码
DeviceControlCard({
  device: device,
  // ...其他属性...
  onTimerStart: (seconds: number) => {
    this.deviceTimerTotal.set(device.deviceId, seconds);
    this.vm.startTimer(device.deviceId, seconds);
  },
  onTimerStop: () => { this.vm.stopTimer(device.deviceId); },
  // ★ 绑定流转回调:从 deviceTimerTotal 取原始总秒数
  onWatchTransfer: () => {
    const total: number = this.deviceTimerTotal.get(device.deviceId) ?? device.timerSeconds;
    console.info(`[KitchenDevicePage] ⌚ 流转: device=${device.deviceId}, name=${device.name}, remaining=${device.timerSeconds}, total=${total}`);
    this.handleWatchTransfer(device.deviceId, device.name, device.timerSeconds, total);
  }
})

核心点解读

  • "先停止手机端计时,再发起手表流转"的顺序至关重要 :如果反过来,可能出现手机端 setInterval 仍在递减而手表端也在递减的双端竞态,导致两个设备显示的剩余时间不一致。
  • 手表状态指示条位于页面顶部,全局可见:不是嵌在某个设备卡片里------流转后用户切换到"设备控制"页或其他 Tab,仍然能看到手表上的计时进度。
  • onClick(this.onWatchTransfer) 直接传 @Event 引用 :与 ArkUI 原生 onClick 签名匹配,无需额外箭头函数包装,避免 ?.() 可选链或闭包捕获过期引用的问题。
  • deviceTimerTotal Map 记录原始总秒数onTimerStart 时存入原始值(300 / 900),onWatchTransfer 时取出作为 totalSeconds,与当前剩余秒数分开传递。

五、代码交付清单

文件 新增/修改 职责
services/WatchServiceHelper.ets 新增 手表通信封装:设备发现、数据传输、震动控制、模拟手表端倒计时
business/WatchTimerManager.ets 新增 流转状态机:6 种状态管理、transferToWatch 核心方法、Service 回调桥接
components/DeviceControlCard.ets 修改 ★ v3 新增 ⌚ 流转至手表 按钮和 @Event onWatchTransfer 回调
pages/KitchenDevicePage.ets 修改 ★ v3 新增手表连接状态、流转状态指示条、生命周期集成

六、设计决策

决策 选择 理由
流转后手机端计时 立即停止 避免双端竞态。手表是单源倒计时,手机端仅被动显示进度
手表连接可用性检查 isWorn 穿戴检测 手表摘下放桌上时震动无效,必须在佩戴状态下才允许流转
流转失败恢复策略 状态回退到 COUNTING_ON_PHONE 不丢失计时进度,用户不必重新设置
手表震动时机 最后 10 秒 + 完成 三段式触觉反馈:安静→预警→告警,不依赖屏幕
手机端流转状态位置 页面顶部全局指示条 用户切换到其他 Tab 也能看到手表上的计时进度
preservedSeconds Business 层保存 流转时记录的剩余秒数,供取消流转时恢复使用
是否支持手表操作"暂停" 不支持 设计原则:手表专职感知(看时间 + 感受震动),手机专职控制(开始/停止/流转)
模拟实现策略 API 签名对齐真实 @kit.WearEngine 后续接入真机仅需取消注释 import 并替换 transferTimer 内部实现

七、运行与结果验证

7.1 操作步骤

  1. 部署到真机或者模拟器。

  2. 进入厨电控制页,点击某台电磁炉的**「定时 5 分钟」**按钮 → 手机端倒计时开始显示。

  3. 在计时器显示区域点击**「⌚ 流转至手表」**按钮 → 手机端计时器停止,页面顶部出现橙色状态条"电磁炉 计时中 · HUAWEI WATCH GT 4 · 剩余 04:58"。

  4. 等待倒计时进入最后 10 秒 → 日志显示手表渐强震动。

  5. 倒计时归零 → 状态条变绿"电磁炉 计时完成!",日志显示手表重复震动。

7.2 预期结果

操作 预期 UI 变化 预期日志
点击「定时 5 分钟」 设备卡片显示 "⏱️ 05:00" [KitchenDeviceVM] 启动计时器: dev_02 → 300 秒
点击「⌚ 流转至手表」 手机端计时消失,顶部出现橙色条 [KitchenDevicePage] 🔄 流转计时器[WatchTimerManager] 状态: countingOnPhone → transferringToWatch → countingOnWatch
手表倒计时中每秒 橙色条实时更新剩余时间 [WatchServiceHelper] 手表端倒计时: 287 → 286 → ...
剩余 10 秒 日志显示渐强震动 [WatchServiceHelper] ⏰ 倒计时最后 10 秒,手表渐强震动...
倒计时归零 橙色条变绿"计时完成" [WatchServiceHelper] ⌛ 手表端计时完成,已触发震动提醒
点击「清除」 绿色条消失,状态回 IDLE [WatchTimerManager] 状态: completedOnWatch → idle

7.3 控制台完整日志

复制代码
[KitchenDevicePage] 初始化手表计时器服务
[WatchServiceHelper] 初始化手表服务...
[WatchServiceHelper] 发现手表: HUAWEI WATCH GT 4 (PNX-B19), 电量: 85%
[KitchenDevicePage] 手表已连接: HUAWEI WATCH GT 4
[KitchenDeviceVM] 启动计时器: dev_02 → 300 秒
[MockDS] 电磁炉 计时器: 300秒
...(手机端倒计时: 299, 298, 297...)
[KitchenDevicePage] 🔄 流转计时器: 电磁炉, 剩余 287秒
[KitchenDeviceVM] 停止计时器: dev_02
[WatchTimerManager] 状态变更: countingOnPhone → transferringToWatch
[WatchServiceHelper] ⏱️ 计时器流转至手表: "电磁炉" → 287秒
[WatchServiceHelper] ✅ 计时器已成功流转至手表,手表端开始倒计时
[WatchTimerManager] 状态变更: transferringToWatch → countingOnWatch
[KitchenDevicePage] ✅ 计时器已流转至手表
...(手表端倒计时: 286, 285, 284...)
[WatchServiceHelper] ⏰ 倒计时最后 10 秒,手表渐强震动...
[WatchServiceHelper] 📳 手表震动: 渐强震动, 500ms
...(手表端倒计时: 3, 2, 1, 0)
[WatchServiceHelper] 📳 手表震动: 重复震动, 3000ms
[WatchServiceHelper] ⌛ 手表端计时完成,已触发震动提醒
[WatchTimerManager] 状态变更: countingOnWatch → completedOnWatch
[KitchenDevicePage] ⌛ 手表计时完成!

验证要点

  • 流转后手机端的 setInterval 确认已停止------不会再看到 [MockDS] 电磁炉 计时器: X秒 的日志。
  • 手表端倒计时独立运行,手机端通过 onWatchTimerStateChange 回调被动更新。
  • 渐强震动在剩余 10 秒时准确触发,重复震动在归零时触发。

八、本阶段总结与下篇预告

今天,我们为《灵犀厨房》装上了"手腕大脑"------实现了烹饪计时器从手机流转至智能手表的完整链路:

  • 封装 WatchServiceHelper :将 @kit.WearEngine 的手表通信 API 包装为服务层,实现设备发现、计时数据传输、三段式震动控制,完整保留 API 23 的真实接口签名。
  • 构建 WatchTimerManager :定义 6 种流转状态 + 状态机,transferToWatch 协调"停止手机计时→发起手表流转"的全流程,preservedSeconds 预留恢复能力。
  • 关键设计决策------手表专职感知,手机专职控制:流转后手机端立即停止计时,手表端独立倒计时并做震动反馈。不支持手表端操作"暂停/停止",保持"手机发起、手表感知"的简洁交互模型。
  • 集成流转 UI:厨电控制页顶部全局状态指示条 + 设备卡片内"⌚ 流转至手表"按钮 + 手表连接电量显示。

现在,你在厨房给电磁炉定时 15 分钟煮汤,点一下「⌚ 流转至手表」------然后去客厅看电视。手腕一抬,剩余时间一目了然;最后 10 秒渐强震动让你从容走向厨房;计时完成,重复震动告诉你:该关火了

但烹饪中的提醒不止计时------明天要做红烧肉但忘了买五花肉?炖汤该加盐了但正在切菜腾不出手?

下篇预告 :第 19 篇《通知系统:多设备推送提醒》。我们将接入 @kit.NotificationKit,实现购物清单到期提醒、计时完成跨设备推送、烹饪小贴士定时弹窗------让《灵犀厨房》从"被动查询"变为"主动告知"。


📚 本系列持续更新中:下一篇将实现多设备通知推送,让烹饪提醒无处不在。

🔗 专栏入口:[《HarmonyOS6.1全场景实战》合集]

📦 获取基线版本源码包包括第1-15篇所有代码 + 架构文档 + Flask 后端

如果你觉得这篇文章对你有帮助,请不要吝啬你的点赞 👍、收藏 ⭐ 和评论 💬。你的支持,是我继续输出高质量技术内容的全部动力。
纯血鸿蒙,手腕掌控。我们下一篇见!

相关推荐
华普微HOPERF2 天前
智能手表集成数字气压传感器,就能实现楼层定位功能?
人工智能·计算机视觉·智能手表
若兰幽竹4 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(十七):【语音识别】免提声控启动播报——动口不动手
语音识别·华为鸿蒙系统·harmonyos6.1.0·灵犀厨房
若兰幽竹6 天前
【HarmonyOS6.1全场景实战】基线版本:我用了15篇文章,造出了一个能登录、能推荐、带后台的鸿蒙全栈App
华为鸿蒙系统·harmonyos6.1.0·灵犀厨房
学习使我健康6 天前
智能手表与 App 蓝牙低功耗(BLE)实战指南
智能手表
若兰幽竹6 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(十五)之【超级设备模拟器实战】多设备交互调试:像上帝一样俯瞰整个智能厨房
华为鸿蒙系统·harmonyos6.1.0·灵犀厨房
若兰幽竹7 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(十四)之【分布式流转】让菜谱“飞”:手机选、平板看、智慧屏播的全场景秘诀
分布式·华为鸿蒙系统·harmonyos6.1.0·灵犀厨房
若兰幽竹7 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(十三)之【智能厨电模拟】用代码“凭空”创造智能厨房:《灵犀厨房》的全场景前奏
华为鸿蒙系统·harmonyos6.1.0·灵犀厨房
若兰幽竹7 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(十二)之【营养分析引擎】计算个性化卡路里建议:给《灵犀厨房》装上“营养大脑”
华为鸿蒙系统·harmonyos6.1.0·灵犀厨房
若兰幽竹9 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战之补充【架构进化】灵犀厨房四层分层设计:给鸿蒙 App 搭一副坚不可摧的骨架
架构·鸿蒙系统·harmonyos6.1.0·灵犀厨房