鸿蒙用药提醒(定时弹窗/语音播报)详解
【摘要】 一、引言在慢性病管理、术后康复等医疗场景中,按时、准确用药是保障治疗效果的关键。然而,患者常因忙碌、遗忘或复杂用药方案(如“每日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)