鸿蒙用药提醒(定时弹窗/语音播报)详解
        【摘要】 一、引言在慢性病管理、术后康复等医疗场景中,按时、准确用药是保障治疗效果的关键。然而,患者常因忙碌、遗忘或复杂用药方案(如“每日3次,每次2片,饭前服用”)导致漏服或错服药物。传统用药提醒方式(如手机闹钟、纸质便签)存在 提醒单一(仅声音)、缺乏上下文(如药品名称、剂量)、跨设备同步困难 等问题。鸿蒙操作系统(HarmonyOS)凭借 分布式软总线、定时任务管理、语音合成...
    
    
    
    一、引言
二、技术背景
1. 鸿蒙核心技术支撑
- 
定时任务管理(AlarmManager):支持设置精确的定时任务(如每天9:00、14:00触发),可在设备休眠时唤醒应用进程,确保提醒准时触发。  - 
分布式软总线:实现手机、车机、平板等鸿蒙设备间的 低延迟通信,用药提醒数据(如药品名称、剂量、提醒时间)通过分布式数据库同步,多设备共享最新状态。  - 
语音合成与播报(TTS,Text-To-Speech):鸿蒙内置语音引擎,可将文字信息(如“请服用阿司匹林肠溶片,每日2次,每次1片”)转换为自然语音,通过设备扬声器播放。  - 
原子化服务:用药提醒功能可封装为 鸿蒙原子化服务卡片,用户通过负一屏或桌面快捷入口快速查看当日用药计划,支持一键设置或修改提醒。  
2. 用药提醒的业务挑战
- 
复杂用药方案的精准表达:需支持“每日多次、不同剂量、与饮食关联(如饭前/饭后)”等详细规则,传统闹钟仅能设置固定时间,无法关联具体药品信息。  - 
多设备协同提醒:用户在手机上设置提醒后,若驾车时需通过车机接收语音播报,或在居家时通过平板查看弹窗+语音双重提醒,传统应用需手动同步数据。  - 
提醒方式的个性化适配:不同场景下用户偏好不同(如驾车时优先语音播报,居家时需弹窗+语音确认),需根据设备类型和用户状态动态调整提醒策略。  
三、应用使用场景
1. 定时弹窗提醒(居家/办公场景)
2. 语音播报提醒(车载/移动场景)
3. 跨设备同步提醒(多终端协同)
四、不同场景下详细代码实现
场景 1:定时弹窗提醒(手机端)
1.1 核心代码实现(MedicationReminder.ets)
// src/main/ets/pages/MedicationReminder.ets
import reminderManager from '../utils/ReminderManager.ets';
import promptAction from '@ohos.promptAction';
@Entry
@Component
struct MedicationReminder {
  @State private reminderManager: reminderManager.ReminderManager = new reminderManager.ReminderManager(this.context);
  @State private reminders: Array<MedicationReminderItem> = []; // 用药提醒列表
  @State private showReminderPopup: boolean = false; // 是否显示弹窗
  @State private currentReminder: MedicationReminderItem | null = null; // 当前弹窗提醒项
  // 用药提醒数据结构
  interface MedicationReminderItem {
    id: string;
    drugName: string; // 药品名称
    dose: string; // 剂量(如“1片”)
    time: string; // 服用时间(如“09:00”)
    instruction: string; // 服用说明(如“饭前”)
    isTaken: boolean; // 是否已服用
  }
  aboutToAppear() {
    this.loadReminders();
    this.startReminderListener();
  }
  // 加载本地用药提醒(从分布式数据库或本地存储)
  private loadReminders() {
    this.reminderManager.getReminders().then((fetchedReminders) => {
      this.reminders = fetchedReminders || [];
    }).catch((error) => {
      console.error('加载用药提醒失败:', error);
      this.reminders = [];
    });
  }
  // 启动提醒监听(监听定时任务触发)
  private startReminderListener() {
    // 实际项目中通过 AlarmManager 监听定时任务,此处简化为每秒检查当前时间
    setInterval(() => {
      const now = new Date();
      const currentTime = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
      
      this.reminders.forEach((reminder) => {
        if (!reminder.isTaken && reminder.time === currentTime) {
          this.triggerReminderPopup(reminder);
        }
      });
    }, 1000); // 每秒检查一次(实际用 AlarmManager 更高效)
  }
  // 触发弹窗提醒
  private triggerReminderPopup(reminder: MedicationReminderItem) {
    this.currentReminder = reminder;
    this.showReminderPopup = true;
  }
  // 确认已服用
  private confirmTaken() {
    if (this.currentReminder) {
      this.currentReminder.isTaken = true;
      this.reminderManager.updateReminder(this.currentReminder).then(() => {
        promptAction.showToast({ message: '已标记为服用' });
      }).catch((error) => {
        console.error('更新服用状态失败:', error);
      });
      this.closePopup();
    }
  }
  // 关闭弹窗
  private closePopup() {
    this.showReminderPopup = false;
    this.currentReminder = null;
  }
  build() {
    Column() {
      Text('用药提醒')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 });
      if (this.reminders.length === 0) {
        Text('暂无用药提醒')
          .fontSize(16)
          .fontColor('#999')
          .margin({ top: 50 });
      } else {
        List() {
          ForEach(this.reminders, (reminder: MedicationReminderItem) => {
            ListItem() {
              Row() {
                Column() {
                  Text(`${reminder.drugName}`)
                    .fontSize(16)
                    .fontWeight(FontWeight.Medium);
                  Text(`剂量: ${reminder.dose}`)
                    .fontSize(14)
                    .fontColor('#666');
                  Text(`时间: ${reminder.time}`)
                    .fontSize(14)
                    .fontColor('#666');
                  Text(`说明: ${reminder.instruction}`)
                    .fontSize(14)
                    .fontColor('#666');
                  Text(`状态: ${reminder.isTaken ? '已服用' : '未服用'}`)
                    .fontSize(14)
                    .fontColor(reminder.isTaken ? '#28a745' : '#dc3545');
                }
                .layoutWeight(1);
                if (!reminder.isTaken) {
                  Button('标记服用')
                    .onClick(() => {
                      this.confirmTaken();
                    })
                    .margin({ left: 10 });
                }
              }
              .width('100%')
              .padding(15)
              .backgroundColor('#f8f9fa')
              .borderRadius(8)
              .margin({ bottom: 10 });
            }
          });
        }
        .layoutWeight(1)
        .padding(10);
      }
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .overlay(
      this.showReminderPopup ? 
      this.buildReminderPopup() : 
      null
    );
  }
  // 构建弹窗提醒UI
  @Builder
  buildReminderPopup() {
    Column() {
      Text(`用药提醒`)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 15 });
      Text(`药品: ${this.currentReminder?.drugName}`)
        .fontSize(16)
        .margin({ bottom: 10 });
      Text(`剂量: ${this.currentReminder?.dose}`)
        .fontSize(16)
        .margin({ bottom: 10 });
      Text(`时间: ${this.currentReminder?.time}`)
        .fontSize(16)
        .margin({ bottom: 10 });
      Text(`说明: ${this.currentReminder?.instruction}`)
        .fontSize(16)
        .margin({ bottom: 20 });
      Row() {
        Button('已服用')
          .onClick(() => {
            this.confirmTaken();
          })
          .margin({ right: 10 });
        Button('稍后提醒')
          .onClick(() => {
            this.closePopup();
          })
          .backgroundColor('#6c757d');
      }
      .width('100%')
      .justifyContent(FlexAlign.End);
    }
    .width('80%')
    .height('40%')
    .backgroundColor(Color.White)
    .borderRadius(12)
    .padding(20)
    .shadow({
      radius: 10,
      color: '#00000020',
      offsetX: 0,
      offsetY: 4
    })
    .animation({
      duration: 300,
      curve: Curve.EaseInOut
    });
  }
}
1.2 分布式提醒管理工具类(ReminderManager.ets)
// src/main/ets/utils/ReminderManager.ets
import distributedData from '@ohos.data.distributedData';
import { BusinessError } from '@ohos.base';
export class ReminderManager {
  private context: any; // 鸿蒙上下文
  constructor(context: any) {
    this.context = context;
  }
  // 获取所有用药提醒(从分布式数据库)
  public async getReminders(): Promise<Array<MedicationReminder.ets['MedicationReminderItem']>> {
    try {
      const preferences = await distributedData.getPreferences(this.context, 'EMR_MedicationReminders');
      const remindersJson = await preferences.get('reminders', '[]');
      const reminders: Array<MedicationReminder.ets['MedicationReminderItem']> = JSON.parse(remindersJson);
      return reminders;
    } catch (error) {
      const err = error as BusinessError;
      console.error('获取用药提醒失败,错误码: ${err.code}, 消息: ${err.message}');
      throw new Error('分布式数据库读取失败');
    }
  }
  // 更新用药提醒状态(如标记为已服用)
  public async updateReminder(reminder: MedicationReminder.ets['MedicationReminderItem']) {
    try {
      const preferences = await distributedData.getPreferences(this.context, 'EMR_MedicationReminders');
      const existingRemindersJson = await preferences.get('reminders', '[]');
      const existingReminders: Array<MedicationReminder.ets['MedicationReminderItem']> = JSON.parse(existingRemindersJson);
      
      // 找到对应提醒并更新状态
      const index = existingReminders.findIndex(r => r.id === reminder.id);
      if (index !== -1) {
        existingReminders[index] = reminder;
        await preferences.put('reminders', JSON.stringify(existingReminders));
        await preferences.flush(); // 同步到分布式节点
      }
    } catch (error) {
      const err = error as BusinessError;
      console.error('更新用药提醒失败,错误码: ${err.code}, 消息: ${err.message}');
      throw new Error('分布式数据库写入失败');
    }
  }
  // 添加新的用药提醒(示例:用户设置新提醒时调用)
  public async addReminder(reminder: Omit<MedicationReminder.ets['MedicationReminderItem'], 'id' | 'isTaken'>) {
    try {
      const preferences = await distributedData.getPreferences(this.context, 'EMR_MedicationReminders');
      const existingRemindersJson = await preferences.get('reminders', '[]');
      const existingReminders: Array<MedicationReminder.ets['MedicationReminderItem']> = JSON.parse(existingRemindersJson);
      
      const newReminder: MedicationReminder.ets['MedicationReminderItem'] = {
        id: `rem_${Date.now()}`,
        ...reminder,
        isTaken: false
      };
      existingReminders.push(newReminder);
      await preferences.put('reminders', JSON.stringify(existingReminders));
      await preferences.flush();
    } catch (error) {
      const err = error as BusinessError;
      console.error('添加用药提醒失败,错误码: ${err.code}, 消息: ${err.message}');
      throw new Error('分布式数据库写入失败');
    }
  }
}
- 
用户在手机端设置用药提醒(如“每日9:00,阿司匹林肠溶片1片,饭前”),到达9:00时,手机屏幕弹出提醒窗口,显示药品名称、剂量、服用说明及状态;  - 
用户点击“已服用”后,状态更新为“已服用”,并通过分布式数据库同步至其他设备(如车机);  - 
若用户未及时处理,可点击“稍后提醒”关闭弹窗(实际项目中可设置延迟提醒)。  
场景 2:语音播报提醒(车机端)
2.1 核心代码实现(VoiceMedicationReminder.ets)
// src/main/ets/pages/VoiceMedicationReminder.ets
import reminderManager from '../utils/ReminderManager.ets';
import media from '@ohos.multimedia.media';
import speechSynthesis from '@ohos.speech.synthesis';
@Entry
@Component
struct VoiceMedicationReminder {
  @State private reminderManager: reminderManager.ReminderManager = new reminderManager.ReminderManager(this.context);
  @State private reminders: Array<MedicationReminder.ets['MedicationReminderItem']> = [];
  @State private isPlayingSpeech: boolean = false; // 是否正在语音播报
  // 用药提醒数据结构(同手机端)
  interface MedicationReminderItem {
    id: string;
    drugName: string;
    dose: string;
    time: string;
    instruction: string;
    isTaken: boolean;
  }
  aboutToAppear() {
    this.loadReminders();
    this.startVoiceReminderListener();
  }
  // 加载用药提醒(从分布式数据库)
  private loadReminders() {
    this.reminderManager.getReminders().then((fetchedReminders) => {
      this.reminders = fetchedReminders || [];
    }).catch((error) => {
      console.error('加载用药提醒失败:', error);
      this.reminders = [];
    });
  }
  // 启动语音提醒监听(定时检查并播报)
  private startVoiceReminderListener() {
    setInterval(() => {
      const now = new Date();
      const currentTime = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
      
      this.reminders.forEach((reminder) => {
        if (!reminder.isTaken && reminder.time === currentTime && !this.isPlayingSpeech) {
          this.triggerVoiceReminder(reminder);
        }
      });
    }, 1000); // 每秒检查一次
  }
  // 触发语音播报提醒
  private async triggerVoiceReminder(reminder: MedicationReminderItem) {
    this.isPlayingSpeech = true;
    try {
      // 构造语音播报文本(示例:更自然的表达)
      const speechText = `请服用${reminder.drugName},每日${this.getFrequencyText(reminder)},本次为${this.getTimePeriod(reminder.time)}剂量,${reminder.instruction}服用。`;
      
      // 调用鸿蒙语音合成API(实际项目中需初始化语音引擎)
      await this.speakText(speechText);
      
      // 播报完成后,可选择自动弹出确认按钮(或等待用户手动操作)
      setTimeout(() => {
        this.showVoiceReminderPopup(reminder);
      }, 3000); // 播报后3秒显示确认弹窗
    } catch (error) {
      console.error('语音播报失败:', error);
      this.showVoiceReminderPopup(reminder); // 播报失败时直接显示弹窗
    } finally {
      this.isPlayingSpeech = false;
    }
  }
  // 语音合成播报(简化实现,实际需使用鸿蒙 speechSynthesis 模块)
  private async speakText(text: string) {
    return new Promise((resolve, reject) => {
      // 实际代码示例(需导入 @ohos.speech.synthesis):
      // const synthesizer = speechSynthesis.createSynthesizer();
      // synthesizer.start({
      //   text: text,
      //   onSuccess: () => resolve(),
      //   onError: (err) => reject(err)
      // });
      console.log(`【语音播报】${text}`); // 简化:控制台输出代替实际播报
      setTimeout(resolve, 2000); // 模拟2秒播报时间
    });
  }
  // 显示语音提醒弹窗(用户确认)
  private showVoiceReminderPopup(reminder: MedicationReminderItem) {
    // 实际项目中可通过 overlay 弹出确认窗口(类似手机端弹窗)
    promptAction.showToast({ message: `语音提醒:${reminder.drugName} 已播报,请确认是否服用` });
    // 简化:直接更新状态(实际需用户交互)
    setTimeout(() => {
      this.updateReminderStatus(reminder);
    }, 5000); // 5秒后自动标记为已服用(示例逻辑,实际需用户确认)
  }
  // 更新提醒状态(标记为已服用)
  private async updateReminderStatus(reminder: MedicationReminderItem) {
    reminder.isTaken = true;
    this.reminderManager.updateReminder(reminder).then(() => {
      console.log('语音提醒状态已更新');
    }).catch((error) => {
      console.error('更新语音提醒状态失败:', error);
    });
  }
  // 辅助方法:获取用药频率文本(简化)
  private getFrequencyText(reminder: MedicationReminderItem): string {
    // 实际根据业务逻辑解析(如“每日2次”)
    return '2次';
  }
  // 辅助方法:获取时间段描述(简化)
  private getTimePeriod(time: string): string {
    if (time >= '06:00' && time < '12:00') return '上午';
    if (time >= '12:00' && time < '18:00') return '下午';
    return '晚上';
  }
  build() {
    Column() {
      Text('语音用药提醒')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 });
      Text('语音播报功能运行中(到达时间自动提醒)')
        .fontSize(16)
        .fontColor('#666');
    }
    .width('100%')
    .height('100%')
    .padding(20);
  }
}
- 
车机端在设定时间(如14:00)自动触发语音播报,播报内容为“请服用降压药,每日2次,本次为下午剂量,饭后服用”;  - 
播报完成后,用户可通过语音回复“已服用”(需集成语音识别)或等待自动确认(示例中简化为5秒后标记为已服用);  - 
语音提醒状态通过分布式数据库同步至手机端,确保多设备数据一致。  
五、原理解释
1. 鸿蒙用药提醒的核心流程
- 
提醒设置与存储: - 
用户在手机或车机上设置用药提醒(药品名称、剂量、时间、说明),通过 ReminderManager工具类将提醒数据(包含唯一ID、状态等)保存到 分布式数据库(如EMR_MedicationReminders),数据标识符确保多设备可访问同一份提醒列表。 
 - 
 - 
定时触发与监听: - 
应用通过 定时任务监听机制(示例中简化为每秒检查当前时间,实际项目推荐使用鸿蒙的 AlarmManager实现精准定时)监听当前时间,当到达设定时间(如“09:00”)时,触发对应提醒项。 
 - 
 - 
多模态提醒: - 
手机端:触发 弹窗提醒,显示药品详情(名称、剂量、时间、说明)和操作按钮(“已服用”“稍后提醒”),用户点击后更新状态并同步至分布式数据库。  - 
车机端:触发 语音播报,通过鸿蒙的语音合成API(如 speechSynthesis)将提醒文本转换为自然语音播放,播报后显示确认弹窗或自动更新状态。 
 - 
 - 
跨设备同步: - 
所有设备(手机、车机、平板)通过分布式数据库实时同步提醒状态(如“已服用”“未服用”),当用户在手机端标记某提醒为“已服用”时,车机端自动更新该提醒的状态,避免重复提醒。  
 - 
 - 
状态管理: - 
每个提醒项包含唯一ID、药品信息、服用时间、状态(是否已服用)等字段,通过分布式数据库的 getPreferences和put方法实现数据的持久化与同步。 
 - 
 
2. 关键技术点
- 
分布式数据同步:用药提醒数据通过分布式数据库(Preferences)存储,数据标识符(如 EMR_MedicationReminders)唯一标识提醒集合,确保手机、车机、平板等设备访问同一份数据,实现状态实时同步。 - 
精准定时触发:实际项目中应使用鸿蒙的 AlarmManager(支持后台唤醒和精确时间触发),替代示例中的每秒检查逻辑,保障提醒准时到达(如即使设备休眠也能触发)。 - 
语音合成与播报:车机端通过鸿蒙的 speechSynthesis模块将文字提醒转换为语音,支持多语言和自然语调,提升用户体验;手机端可扩展语音播报功能(如用户选择“语音+弹窗”双提醒)。 - 
用户交互与状态确认:用户通过点击“已服用”或语音回复确认用药,应用更新提醒状态并同步至分布式数据库,确保后续提醒逻辑准确(如已服用的药品不再重复提醒)。  
六、核心特性
| 
 | 
 | 
|---|---|
| 
 | 
 | 
| 
 | 
 | 
| 
 | 
 | 
| 
 | 
 | 
| 
 | 
 | 
| 
 | 
 | 
七、原理流程图及原理解释
原理流程图(用药提醒的整体流程)
+-----------------------+       +-----------------------+       +-----------------------+
|  用户设置提醒(手机/车机) |       |  分布式数据库(DDS)  |       |  定时任务监听         |
|  (药品/时间/剂量)    | ----> |  (存储提醒信息)     | ----> |  (检查当前时间)     |
+-----------------------+       +-----------------------+       +-----------------------+
          |                             |                             |
          |  保存提醒数据(JSON)      |  数据持久化与同步         |  触发对应提醒项
          |--------------------------->|                         |
          |                             |  通过分布式软总线广播     |  手机:弹窗提醒
          |  返回成功状态             |                         |  车机:语音播报
          |                             |  多设备实时同步状态       |  更新状态至数据库
原理解释
- 
数据存储:用户设置的用药提醒(包含药品名称、剂量、时间、说明等)通过 ReminderManager工具类保存到分布式数据库(如EMR_MedicationReminders),数据以 JSON 格式存储,每个提醒项包含唯一ID和状态(是否已服用)。 - 
定时监听:应用通过定时任务(示例中为每秒检查,实际用 AlarmManager)监听当前时间,当到达设定时间(如“09:00”)时,遍历所有未服用的提醒项,触发对应设备的提醒方式(弹窗或语音)。 
            【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
                cloudbbs@huaweicloud.com
                
            
        
        
        
        
        
        
        - 点赞
 - 收藏
 - 关注作者
 
            
           
评论(0)