引言

随着智能穿戴设备的普及,用户对跨设备协同体验的要求越来越高。在HarmonyOS 6分布式系统中,手机应用能够主动向已连接的穿戴设备推送通知,实现了真正的设备间无缝协同。这种能力不仅限于简单的消息转发,而是通过模板化通知提供了标准化、可交互、可定制的通知体验。

根据华为官方技术文档,手机应用向穿戴设备推送模板通知的核心是通过WearEngine Kit的NotifyClient.notify()方法实现的。这个功能让开发者能够在穿戴设备上展示包含标题、内容、按钮等元素的标准化通知,即使用户的穿戴设备上没有安装对应的应用,也能够正常接收和显示这些通知。

技术背景与原理

穿戴服务架构

HarmonyOS的Wear Engine Kit(穿戴服务)是连接手机与穿戴设备的桥梁。它基于分布式软总线技术,实现了设备间的能力共享与数据同步。架构主要包括:

  1. 设备管理模块:负责设备发现、连接和管理

  2. 通信能力模块:支持文件传输和消息通知

  3. 传感器能力模块:提供传感器数据访问

  4. 意图框架:通过全局意图理解用户需求

模板通知特点

与传统通知相比,模板通知具有以下优势:

  • 格式标准化:预定义模板确保在不同设备上显示一致

  • 交互丰富:支持最多三个自定义按钮

  • 振动可定制:提供三种重要性级别的振动模式

  • 设备兼容:无需穿戴设备侧安装对应应用

工作原理

手机应用向穿戴设备推送模板通知的完整流程如下:

应用请求 → Wear Engine SDK → 分布式软总线 → 穿戴设备服务 → 设备显示
    ↓           ↓               ↓             ↓             ↓
生成通知 → 封装协议消息 → 跨设备传输 → 解析渲染 → 用户交互

开发环境准备

权限申请与配置

在开始开发前,需要完成以下配置:

1. 申请Wear Engine服务权限

在华为开发者联盟申请开通Wear Engine服务权限,这是使用所有穿戴相关API的前提条件。

2. 配置应用权限

在应用的module.json5文件中添加权限声明:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.NOTIFICATION_CONTROLLER",
        "reason": "用于向穿戴设备发送通知"
      },
      {
        "name": "ohos.permission.DISTRIBUTED_DATASYNC",
        "reason": "用于跨设备数据同步"
      }
    ]
  }
}

3. 集成Wear Engine SDK

在项目的build.gradle文件中添加依赖:

dependencies {
    implementation 'com.huawei.wearable:wear-engine:6.0.0.300'
}

设备兼容性

支持模板通知的设备包括:

  • HUAWEI WATCH系列(H5546、H7546、H5556、H5756、H3540、H9D20)

  • HUAWEI Band系列(HA590、HA5A0)

注意事项

  • HUAWEI WATCH B7-536与B7-738在超长续航模式下不支持

  • 应用包名长度不应超过28个字符

  • 穿戴设备侧无需安装对应应用

完整实现方案

核心类与接口

主要使用的API接口包括:

// 穿戴引擎管理类
import wearEngine from '@ohos.wearEngine';

// 设备信息接口
interface DeviceInfo {
  deviceId: string;      // 设备唯一标识
  deviceName: string;    // 设备名称
  deviceType: number;    // 设备类型
  isConnected: boolean;  // 连接状态
}

// 通知选项接口
interface NotificationOptions {
  templateId: number;    // 模板ID
  pkgName: string;      // 应用包名
  title: string;        // 通知标题
  content: string;      // 通知内容
  remindType?: number;  // 提醒类型
  button?: {           // 按钮配置
    button_1?: string;
    button_2?: string;
    button_3?: string;
  };
}

实现步骤

步骤1:获取已连接设备列表
import wearEngine from '@ohos.wearEngine';
import { BusinessError } from '@ohos.base';

class WearNotificationService {
  private notifyClient: wearEngine.NotifyClient | undefined;
  private connectedDevices: wearEngine.DeviceInfo[] = [];
  
  // 初始化通知客户端
  async initNotifyClient(context: Context): Promise<boolean> {
    try {
      this.notifyClient = wearEngine.getNotifyClient(context);
      console.info('通知客户端初始化成功');
      return true;
    } catch (err) {
      const businessErr = err as BusinessError;
      console.error(`初始化失败,错误码: ${businessErr.code}, 错误信息: ${businessErr.message}`);
      return false;
    }
  }
  
  // 获取已连接设备列表
  async getConnectedDevices(): Promise<wearEngine.DeviceInfo[]> {
    if (!this.notifyClient) {
      throw new Error('通知客户端未初始化');
    }
    
    try {
      this.connectedDevices = await this.notifyClient.getConnectedDevices();
      console.info(`获取到${this.connectedDevices.length}个已连接设备`);
      return this.connectedDevices;
    } catch (err) {
      const businessErr = err as BusinessError;
      console.error(`获取设备列表失败,错误码: ${businessErr.code}, 错误信息: ${businessErr.message}`);
      throw err;
    }
  }
}
步骤2:定义NotificationOptions配置参数
// 通知选项构建器
class NotificationOptionsBuilder {
  // 构建无按钮通知
  static buildBasicNotification(
    title: string,
    content: string,
    pkgName: string = 'com.example.myapp'
  ): wearEngine.NotificationOptions {
    return {
      templateId: 50,  // 无按钮模板
      pkgName: pkgName,
      title: this.truncateString(title, 28),      // 标题最多28字节
      content: this.truncateString(content, 400), // 内容最多400字节
      remindType: 2    // 普通重要性模式
    };
  }
  
  // 构建带按钮的通知
  static buildButtonNotification(
    title: string,
    content: string,
    buttons: string[],
    pkgName: string = 'com.example.myapp'
  ): wearEngine.NotificationOptions {
    let templateId: number;
    let buttonConfig: { [key: string]: string } = {};
    
    switch (buttons.length) {
      case 1:
        templateId = 51;
        buttonConfig = { button_1: this.truncateString(buttons[0], 12) };
        break;
      case 2:
        templateId = 52;
        buttonConfig = {
          button_1: this.truncateString(buttons[0], 12),
          button_2: this.truncateString(buttons[1], 12)
        };
        break;
      case 3:
        templateId = 53;
        buttonConfig = {
          button_1: this.truncateString(buttons[0], 12),
          button_2: this.truncateString(buttons[1], 12),
          button_3: this.truncateString(buttons[2], 12)
        };
        break;
      default:
        templateId = 50;
    }
    
    return {
      templateId: templateId,
      pkgName: pkgName,
      title: this.truncateString(title, 28),
      content: this.truncateString(content, 400),
      remindType: 2,
      button: buttonConfig
    };
  }
  
  // 字符串截断工具
  private static truncateString(str: string, maxBytes: number): string {
    const encoder = new TextEncoder();
    const bytes = encoder.encode(str);
    
    if (bytes.length <= maxBytes) {
      return str;
    }
    
    let truncatedBytes = bytes.slice(0, maxBytes);
    while (truncatedBytes[truncatedBytes.length - 1] > 127) {
      truncatedBytes = truncatedBytes.slice(0, -1);
    }
    
    const decoder = new TextDecoder();
    return decoder.decode(truncatedBytes);
  }
}
步骤3:发送通知到穿戴设备
// 通知发送器
class NotificationSender {
  private service: WearNotificationService;
  
  constructor(service: WearNotificationService) {
    this.service = service;
  }
  
  // 发送通知到指定设备
  async sendNotification(
    deviceId: string,
    options: wearEngine.NotificationOptions
  ): Promise<boolean> {
    if (!this.service.notifyClient) {
      throw new Error('通知客户端未初始化');
    }
    
    try {
      // 验证设备连接状态
      const isConnected = await this.checkDeviceConnection(deviceId);
      if (!isConnected) {
        console.error(`设备 ${deviceId} 未连接`);
        return false;
      }
      
      // 验证通知参数
      const validation = this.validateNotificationOptions(options);
      if (!validation.isValid) {
        console.error('通知参数验证失败:', validation.errors);
        return false;
      }
      
      // 发送通知
      await this.service.notifyClient!.notify(deviceId, options);
      
      console.info(`通知发送成功,设备ID: ${deviceId}`);
      return true;
      
    } catch (err) {
      const businessErr = err as BusinessError;
      console.error(`发送通知失败,错误码: ${businessErr.code}, 错误信息: ${businessErr.message}`);
      return false;
    }
  }
  
  // 验证通知参数
  private validateNotificationOptions(options: wearEngine.NotificationOptions): ValidationResult {
    const errors: string[] = [];
    
    // 验证templateId
    if (![50, 51, 52, 53].includes(options.templateId)) {
      errors.push(`templateId必须为50、51、52或53,当前为: ${options.templateId}`);
    }
    
    // 验证包名长度
    if (options.pkgName.length > 28) {
      errors.push(`pkgName长度不能超过28个字符,当前长度: ${options.pkgName.length}`);
    }
    
    // 验证标题长度
    const titleBytes = new TextEncoder().encode(options.title).length;
    if (titleBytes > 28) {
      errors.push(`标题不能超过28字节,当前字节数: ${titleBytes}`);
    }
    
    // 验证内容长度
    const contentBytes = new TextEncoder().encode(options.content).length;
    if (contentBytes > 400) {
      errors.push(`内容不能超过400字节,当前字节数: ${contentBytes}`);
    }
    
    return {
      isValid: errors.length === 0,
      errors: errors
    };
  }
  
  // 检查设备连接状态
  private async checkDeviceConnection(deviceId: string): Promise<boolean> {
    const devices = await this.service.getConnectedDevices();
    return devices.some(device => device.deviceId === deviceId && device.isConnected);
  }
}
步骤4:处理用户反馈
// 用户反馈处理器
class FeedbackHandler {
  // 处理用户操作反馈
  handleUserFeedback(feedbackData: string): void {
    try {
      const feedback = JSON.parse(feedbackData);
      const feedbackType = feedback.feedback;
      
      let action = '';
      switch (feedbackType) {
        case 0:
          action = '未反馈(按HOME键退出或灭屏)';
          break;
        case 1:
          action = '删除消息';
          break;
        case 2:
          action = '点击按钮1';
          break;
        case 3:
          action = '点击按钮2';
          break;
        case 4:
          action = '点击按钮3';
          break;
        default:
          action = '未知操作';
      }
      
      console.info(`用户操作: ${action}`);
      
      // 这里可以根据用户操作执行相应的业务逻辑
      this.executeAction(action, feedback);
      
    } catch (err) {
      console.error('解析用户反馈数据失败:', err);
    }
  }
  
  // 执行对应的业务逻辑
  private executeAction(action: string, feedback: any): void {
    switch (action) {
      case '点击按钮1':
        this.handleButton1Action(feedback);
        break;
      case '点击按钮2':
        this.handleButton2Action(feedback);
        break;
      case '点击按钮3':
        this.handleButton3Action(feedback);
        break;
      case '删除消息':
        this.handleDeleteAction(feedback);
        break;
    }
  }
}

完整示例代码

下面是一个完整的示例,演示如何实现日程提醒功能:

import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@ohos.base';
import wearEngine from '@ohos.wearEngine';

const TAG = 'ScheduleNotificationAbility';
const DOMAIN_NUMBER = 0xFF00;

export default class ScheduleNotificationAbility extends UIAbility {
  private wearService: WearNotificationService | undefined;
  private notificationSender: NotificationSender | undefined;
  private feedbackHandler: FeedbackHandler | undefined;
  
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(DOMAIN_NUMBER, TAG, 'ScheduleNotificationAbility onCreate');
    
    // 初始化服务
    this.initializeServices();
    
    // 设置定时检查
    this.setupScheduleCheck();
  }
  
  // 初始化服务
  private async initializeServices(): Promise<void> {
    try {
      // 1. 初始化穿戴服务
      this.wearService = new WearNotificationService();
      const initSuccess = await this.wearService.initNotifyClient(this.context);
      
      if (!initSuccess) {
        hilog.error(DOMAIN_NUMBER, TAG, '穿戴服务初始化失败');
        return;
      }
      
      // 2. 初始化其他组件
      this.notificationSender = new NotificationSender(this.wearService);
      this.feedbackHandler = new FeedbackHandler();
      
      // 3. 获取已连接设备
      await this.wearService.getConnectedDevices();
      
      hilog.info(DOMAIN_NUMBER, TAG, '服务初始化成功');
      
    } catch (err) {
      const businessErr = err as BusinessError;
      hilog.error(DOMAIN_NUMBER, TAG, 
        `服务初始化异常: code=${businessErr.code}, message=${businessErr.message}`);
    }
  }
  
  // 设置日程检查
  private setupScheduleCheck(): void {
    // 每30分钟检查一次
    setInterval(() => {
      this.checkAndSendNotifications();
    }, 30 * 60 * 1000);
    
    // 立即执行一次
    this.checkAndSendNotifications();
  }
  
  // 检查并发送通知
  private async checkAndSendNotifications(): Promise<void> {
    if (!this.notificationSender || !this.wearService) {
      return;
    }
    
    try {
      // 1. 获取即将开始的日程
      const schedules = await this.getUpcomingSchedules();
      
      if (schedules.length === 0) {
        hilog.info(DOMAIN_NUMBER, TAG, '没有即将开始的日程');
        return;
      }
      
      // 2. 获取已连接设备
      const devices = await this.wearService.getConnectedDevices();
      
      if (devices.length === 0) {
        hilog.warn(DOMAIN_NUMBER, TAG, '没有找到已连接的穿戴设备');
        return;
      }
      
      // 3. 为每个日程发送通知
      for (const schedule of schedules) {
        await this.sendScheduleNotification(schedule, devices);
      }
      
    } catch (err) {
      const businessErr = err as BusinessError;
      hilog.error(DOMAIN_NUMBER, TAG, 
        `通知发送失败: code=${businessErr.code}, message=${businessErr.message}`);
    }
  }
  
  // 获取即将开始的日程
  private async getUpcomingSchedules(): Promise<Schedule[]> {
    // 这里应该是从数据库或网络获取数据的实际逻辑
    // 返回示例数据
    return [
      {
        id: '1',
        title: '团队会议',
        content: '每周项目进度同步会议',
        startTime: Date.now() + 30 * 60 * 1000, // 30分钟后
        buttons: ['参加', '请假']
      },
      {
        id: '2',
        title: '医生预约',
        content: '年度健康检查',
        startTime: Date.now() + 2 * 60 * 60 * 1000, // 2小时后
        buttons: ['确认', '改期', '取消']
      }
    ];
  }
  
  // 发送日程通知
  private async sendScheduleNotification(
    schedule: Schedule, 
    devices: wearEngine.DeviceInfo[]
  ): Promise<void> {
    if (!this.notificationSender) {
      return;
    }
    
    // 构建通知选项
    const options = NotificationOptionsBuilder.buildButtonNotification(
      `提醒: ${schedule.title}`,
      `${schedule.content}\n时间: ${new Date(schedule.startTime).toLocaleTimeString()}`,
      schedule.buttons
    );
    
    // 设置提醒类型
    const minutesLeft = (schedule.startTime - Date.now()) / (60 * 1000);
    if (minutesLeft <= 15) {
      options.remindType = 3; // 最重要
    } else if (minutesLeft <= 60) {
      options.remindType = 2; // 普通
    } else {
      options.remindType = 1; // 次要
    }
    
    // 发送到所有设备
    for (const device of devices) {
      if (device.isConnected) {
        const success = await this.notificationSender.sendNotification(
          device.deviceId,
          options
        );
        
        if (success) {
          hilog.info(DOMAIN_NUMBER, TAG, 
            `日程通知发送成功: ${schedule.title}, 设备: ${device.deviceName}`);
        } else {
          hilog.warn(DOMAIN_NUMBER, TAG, 
            `日程通知发送失败: ${schedule.title}, 设备: ${device.deviceName}`);
        }
      }
    }
  }
  
  onDestroy(): void {
    hilog.info(DOMAIN_NUMBER, TAG, 'ScheduleNotificationAbility onDestroy');
  }
}

// 类型定义
interface Schedule {
  id: string;
  title: string;
  content: string;
  startTime: number;
  buttons: string[];
}

interface ValidationResult {
  isValid: boolean;
  errors: string[];
}

最佳实践与优化

1. 性能优化建议

连接状态管理

// 连接状态监控
class ConnectionMonitor {
  private deviceStatus: Map<string, DeviceStatus> = new Map();
  
  // 定期检查设备状态
  startMonitoring(interval: number = 30000): void {
    setInterval(async () => {
      await this.checkDeviceStatus();
    }, interval);
  }
  
  private async checkDeviceStatus(): Promise<void> {
    const devices = await wearService.getConnectedDevices();
    
    devices.forEach(device => {
      const oldStatus = this.deviceStatus.get(device.deviceId);
      const newStatus: DeviceStatus = {
        isConnected: device.isConnected,
        lastSeen: Date.now()
      };
      
      if (oldStatus?.isConnected !== newStatus.isConnected) {
        this.onStatusChange(device.deviceId, newStatus);
      }
      
      this.deviceStatus.set(device.deviceId, newStatus);
    });
  }
}

通知队列管理

// 通知队列
class NotificationQueue {
  private queue: NotificationTask[] = [];
  private isProcessing = false;
  
  // 添加到队列
  add(task: NotificationTask): void {
    this.queue.push(task);
    this.processQueue();
  }
  
  // 处理队列
  private async processQueue(): Promise<void> {
    if (this.isProcessing || this.queue.length === 0) {
      return;
    }
    
    this.isProcessing = true;
    
    while (this.queue.length > 0) {
      const task = this.queue.shift()!;
      
      try {
        await task.execute();
      } catch (err) {
        console.error('任务执行失败:', err);
        // 重试逻辑
        await this.retryTask(task);
      }
      
      // 控制发送频率
      await this.delay(100);
    }
    
    this.isProcessing = false;
  }
}

2. 错误处理策略

错误分类处理

// 错误处理器
class ErrorHandler {
  static handleError(error: BusinessError, context: string): void {
    const errorCode = error.code;
    
    switch (errorCode) {
      case 1:
        console.error(`${context}: 参数错误 - ${error.message}`);
        this.handleParameterError();
        break;
      case 2:
        console.error(`${context}: 服务不可用 - ${error.message}`);
        this.handleServiceUnavailable();
        break;
      case 7:
        console.error(`${context}: 设备未连接 - ${error.message}`);
        this.handleDeviceDisconnected();
        break;
      default:
        console.error(`${context}: 未知错误 ${errorCode} - ${error.message}`);
        this.handleUnknownError(error);
    }
  }
  
  // 参数错误处理
  private static handleParameterError(): void {
    // 验证并修正参数
  }
  
  // 服务不可用处理
  private static handleServiceUnavailable(): void {
    // 重试或降级处理
  }
  
  // 设备断开处理
  private static handleDeviceDisconnected(): void {
    // 重新连接或通知用户
  }
}

3. 用户体验优化

智能提醒策略

// 智能提醒
class SmartReminder {
  // 根据用户习惯调整提醒时间
  calculateRemindTime(baseTime: number, userHabits: UserHabits): number {
    let remindOffset = 30 * 60 * 1000; // 默认30分钟
    
    switch (userHabits.scheduleType) {
      case 'MEETING':
        remindOffset = 15 * 60 * 1000; // 会议提前15分钟
        break;
      case 'APPOINTMENT':
        remindOffset = 60 * 60 * 1000; // 预约提前1小时
        break;
      case 'REMINDER':
        remindOffset = 5 * 60 * 1000;  // 提醒提前5分钟
        break;
    }
    
    return baseTime - remindOffset;
  }
  
  // 根据设备类型调整通知
  adaptForDevice(deviceType: string, notification: NotificationOptions): NotificationOptions {
    const adapted = { ...notification };
    
    if (deviceType === 'WATCH') {
      // 手表屏幕小,精简内容
      adapted.title = this.truncate(notification.title, 20);
      adapted.content = this.truncate(notification.content, 80);
    } else if (deviceType === 'BAND') {
      // 手环显示空间有限
      adapted.title = this.truncate(notification.title, 15);
      adapted.content = '';
    }
    
    return adapted;
  }
}

常见问题与解决方案

1. 通知发送失败

问题原因

  1. 设备未连接

  2. 权限未授予

  3. 参数错误

  4. 设备不支持

解决方案

  1. 检查设备连接状态

  2. 验证应用权限

  3. 检查通知参数

  4. 确认设备兼容性

2. 通知显示异常

问题现象

  1. 内容截断

  2. 按钮不显示

  3. 格式错乱

解决方案

  1. 确保标题不超过28字节

  2. 确保内容不超过400字节

  3. 按钮文本不超过12字节

  4. 模板ID与按钮数量匹配

3. 性能问题

优化建议

  1. 批量发送通知

  2. 复用设备连接

  3. 实现智能重试

  4. 压缩传输数据

总结

HarmonyOS 6的穿戴服务为开发者提供了强大的跨设备通知能力。通过Wear Engine Kit的NotifyClient接口,手机应用可以主动、实时地向穿戴设备推送模板化通知。本文详细介绍了从环境准备、权限申请到完整实现的各个环节,并提供了完整的代码示例和最佳实践。

关键要点

  1. 必须先申请Wear Engine服务权限

  2. 严格验证通知参数(长度、格式等)

  3. 实现完善的错误处理和重试机制

  4. 考虑设备兼容性和用户体验

未来展望:随着HarmonyOS生态的发展,穿戴设备与手机的协同将更加紧密。开发者可以基于此技术探索更多创新应用场景,为用户提供更好的跨设备体验。

通过本文的指导,开发者可以快速掌握HarmonyOS 6中手机向穿戴设备推送模板通知的核心技术,构建稳定、高效的跨设备通知系统。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐