ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

任务提醒中心 - ArkTS技术完成提醒注册与数据库任务层详解

任务提醒中心 - ArkTS技术完成提醒注册与数据库任务层详解 实例任务提醒中心技术ohos.reminderAgentManager后台代理提醒、kit.ArkDatarelationalStore 任务表 preferences、提醒权限、日历语义一、本篇范围任务提醒中心是「数据库 系统提醒能力」的综合性应用用户创建带提醒时间的任务如「明天 9:00 交周报」任务存 SQLite提醒时间到达时系统弹出通知即使应用不在前台甚至已被杀支持定时提醒每天/每周重复、倒计时提醒、已到期任务自动标记。本篇拆「提醒服务层」核心问题提醒怎么注册reminderAgentManager.publishReminder的 ReminderRequest 类型定时/日历/倒计时与参数应用被杀后提醒还响吗后台代理提醒的机制——提醒由系统进程调度不依赖应用存活提醒怎么管理cancelReminder按 id 取消getValidReminders查询有效提醒任务表怎么设计SQLite 任务表 提醒 id 关联删除任务时同步取消系统提醒提醒权限怎么申请ohos.permission.PUBLISH_AGENT_REMINDER的等级与流程。二、后台代理提醒机制为什么它和通知不同2.1 三种提醒方式的对比方式调度方应用被杀后适用NotificationKit 通知应用内即时发布不响应用没了状态变更提示后台任务长时/延迟应用申请、系统调度受配额限制需要执行代码的任务reminderAgentManager系统提醒服务✅ 正常提醒定时闹钟、日历提醒核心认知reminderAgentManager的提醒不是「应用发通知」而是「把提醒事项交给系统提醒服务」。注册后提醒逻辑跑在系统进程应用被杀死、重启、甚至卸载重装前的提醒都已排期——这是闹钟类应用唯一正确的技术选型。与之对比应用内的setTimeout一杀进程就没了。2.2 权限ohos.permission.PUBLISH_AGENT_REMINDER是system_grantnormal 级API 9 起声明即可用无需运行时弹窗{ module: { requestPermissions: [ { name: ohos.permission.PUBLISH_AGENT_REMINDER, reason: $string:reminder_reason, usedScene: { abilities: [EntryAbility], when: inuse } } ] } }同时建议申请通知权限提醒通知横幅展示需要系统通知开关开启requestEnableNotification在第二篇页面层处理。三、任务数据模型与 SQLite 表3.1 任务模型exportenumRemindType{ONE_TIME1,// 单次提醒DAILY2,// 每天重复WEEKLY3// 每周重复}exportenumTaskStatus{PENDING0,// 待提醒DONE1,// 已完成MISSED2// 已错过时间过了未完成}exportinterfaceTaskItem{id:number;// 数据库自增 idtitle:string;// 任务标题note:string;// 备注remindType:RemindType;remindTime:number;// 首次提醒时间戳msrepeatDays:number[];// 每周重复的星期1-7DAILY 为空status:TaskStatus;reminderId:number;// 系统提醒 id关联 reminderAgentManagercreatedAt:number;}3.2 建表 SQLCREATETABLEIFNOTEXISTStask(idINTEGERPRIMARYKEYAUTOINCREMENT,titleTEXTNOTNULL,noteTEXTDEFAULT,remind_typeINTEGERDEFAULT1,remind_timeINTEGERNOTNULL,repeat_daysTEXTDEFAULT[],-- JSON 数组存星期statusINTEGERDEFAULT0,reminder_idINTEGERDEFAULT-1,created_atINTEGERNOTNULL);repeat_days用 TEXT 存 JSON 数组如[1,3,5]周一三五SQLite 无原生数组类型JSON 字符串是最常见处理。reminder_id是系统提醒的 id删除任务时按它cancelReminder——这是「业务数据 ↔ 系统资源」的关联键。3.3 DAOimport{relationalStore}fromkit.ArkData;import{common}fromkit.AbilityKit;exportclassTaskDao{privatestaticreadonlyDB_NAME:stringtask_reminder.db;staticasyncgetStore(context:common.UIAbilityContext):PromiserelationalStore.RdbStore{conststoreawaitrelationalStore.getRdbStore(context,{name:TaskDao.DB_NAME,securityLevel:relationalStore.SecurityLevel.S1});awaitstore.executeSql(CREATE TABLE IF NOT EXISTS task (...));returnstore;}staticasyncinsert(context:common.UIAbilityContext,task:OmitTaskItem,id):Promisenumber{conststoreawaitTaskDao.getStore(context);constvaluesnewrelationalStore.ValuesBucket();values.put(title,task.title);values.put(note,task.note);values.put(remind_type,task.remindType);values.put(remind_time,task.remindTime);values.put(repeat_days,JSON.stringify(task.repeatDays));values.put(status,task.status);values.put(reminder_id,task.reminderId);values.put(created_at,task.createdAt);constrowIdawaitstore.insert(task,values);returnrowId;}staticasyncqueryAll(context:common.UIAbilityContext):PromiseTaskItem[]{conststoreawaitTaskDao.getStore(context);constprednewrelationalStore.RdbPredicates(task);pred.orderByDesc(remind_time);constrsawaitstore.query(pred);constlist:TaskItem[][];while(rs.goToNextRow()){list.push({id:rs.getLong(rs.getColumnIndex(id)),title:rs.getString(rs.getColumnIndex(title)),note:rs.getString(rs.getColumnIndex(note)),remindType:rs.getLong(rs.getColumnIndex(remind_type)),remindTime:rs.getLong(rs.getColumnIndex(remind_time)),repeatDays:JSON.parse(rs.getString(rs.getColumnIndex(repeat_days))),status:rs.getLong(rs.getColumnIndex(status)),reminderId:rs.getLong(rs.getColumnIndex(reminder_id)),createdAt:rs.getLong(rs.getColumnIndex(created_at))});}rs.close();returnlist;}staticasyncupdateStatus(context:common.UIAbilityContext,id:number,status:TaskStatus):Promisevoid{conststoreawaitTaskDao.getStore(context);constvaluesnewrelationalStore.ValuesBucket();values.put(status,status);constprednewrelationalStore.RdbPredicates(task);pred.equalTo(id,id);awaitstore.update(values,pred);}staticasyncremove(context:common.UIAbilityContext,id:number):Promisevoid{conststoreawaitTaskDao.getStore(context);constprednewrelationalStore.RdbPredicates(task);pred.equalTo(id,id);awaitstore.delete(pred);}}insert返回的自增 rowId 就是任务 id——之后更新提醒 id、状态都靠它定位。ValuesBucket是 SQLite 的写入载体列名 → 值。四、提醒注册ReminderRequest 三兄弟4.1 三种 ReminderRequestimport{reminderAgentManager}fromkit.BackgroundTasksKit;import{BusinessError}fromkit.BasicServicesKit;exportclassReminderService{/** 发布提醒返回系统 reminderId */staticasyncpublish(context:common.UIAbilityContext,task:TaskItem):Promisenumber{letrequest:reminderAgentManager.ReminderRequest;consttimenewDate(task.remindTime);if(task.remindTypeRemindType.ONE_TIME){// 单次日历提醒request{reminderType:reminderAgentManager.ReminderType.REMINDER_TYPE_CALENDAR,dateTime:{year:time.getFullYear(),month:time.getMonth()1,day:time.getDate(),hour:time.getHours(),minute:time.getMinutes(),second:0},title:task.title,content:task.note||task.title,notificationId:task.id,// 通知 id 与任务 id 关联slotType:reminderAgentManager.SlotType.SOCIAL_COMMUNICATION,wantAgent:{pkgName:com.example.taskreminder,abilityName:EntryAbility,parameters:{taskId:task.id}// 点击通知跳回任务},actionButton:[{title:标记完成,type:reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE}]};}elseif(task.remindTypeRemindType.DAILY){// 每天定时提醒时分request{reminderType:reminderAgentManager.ReminderType.REMINDER_TYPE_TIMER,triggerTimeInSeconds:this.dailyTriggerInSeconds(time),title:task.title,content:task.note||task.title,notificationId:task.id,slotType:reminderAgentManager.SlotType.SOCIAL_COMMUNICATION,wantAgent:{pkgName:com.example.taskreminder,abilityName:EntryAbility,parameters:{taskId:task.id}}};}else{// 每周日历 重复策略request{reminderType:reminderAgentManager.ReminderType.REMINDER_TYPE_CALENDAR,dateTime:{...},repeatMonths:[],// 不限制月份repeatDaysOfWeek:task.repeatDays,// [1,3,5]title:task.title,content:task.note||task.title,notificationId:task.id,slotType:reminderAgentManager.SlotType.SOCIAL_COMMUNICATION,wantAgent:{pkgName:com.example.taskreminder,abilityName:EntryAbility,parameters:{taskId:task.id}}};}try{constreminderIdawaitreminderAgentManager.publishReminder(request);console.info(提醒已注册:${reminderId});returnreminderId;}catch(err){consteerrasBusinessError;console.error(提醒注册失败:${e.code}${e.message});return-1;}}}4.2 三种类型的区别与参数类型ReminderType时间表达重复单次CALENDARdateTime年月日时分秒无每天TIMERtriggerTimeInSeconds距触发秒数每天自动循环每周CALENDARdateTime repeatDaysOfWeek指定星期单次提醒用日历型明确年月日每天重复用定时型只关心时分每天固定时刻循环每周重复用日历型 repeatDaysOfWeek[1,3,5] 表示周一三五。repeatDaysOfWeek的取值1周日2周一……7周六注意与 JS 的 getDay() 的 0周日不同必须转换。4.3 wantAgent点击通知跳回应用wantAgent定义「点击提醒通知时打开什么」pkgNameabilityName定位应用入口parameters携带参数taskId。用户点通知 → 打开应用并携带 taskId → 页面读参数定位到对应任务。这是提醒应用「从系统回应用」的唯一桥梁。4.4 actionButton通知上的快捷按钮actionButton给通知加「标记完成」按钮ACTION_BUTTON_TYPE_CLOSE类型会在点击后关闭通知——省去用户进应用操作。按钮点击事件通过 wantAgent 或系统回调带回页面层监听第二篇。五、每天提醒的触发时间换算/** 计算距下一次「该时刻」的秒数定时提醒用 */staticdailyTriggerInSeconds(time:Date):number{constnownewDate();consttargetnewDate(now);target.setHours(time.getHours(),time.getMinutes(),0,0);// 若目标时刻已过顺延到明天if(target.getTime()now.getTime()){target.setDate(target.getDate()1);}returnMath.floor((target.getTime()-now.getTime())/1000);}REMINDER_TYPE_TIMER的triggerTimeInSeconds是「从现在起多少秒后触发」触发后系统按周期每天自动重置无需重复注册。算错一秒提醒要么立即触发要么永远不响——时间换算要仔细先对齐到当天该时分已过则加一天。六、提醒管理取消与查询/** 取消提醒删除任务时必调 */staticasynccancel(reminderId:number):Promisevoid{if(reminderId0)return;try{awaitreminderAgentManager.cancelReminder(reminderId);console.info(提醒已取消:${reminderId});}catch(err){console.error(取消失败:${(errasBusinessError).message});}}/** 查询当前所有有效提醒启动时同步状态用 */staticasyncgetValidReminders():PromisereminderAgentManager.ReminderRequest[]{try{returnawaitreminderAgentManager.getValidReminders();}catch(err){console.error(查询提醒失败:${(errasBusinessError).message});return[];}}删除任务的正确顺序先cancelReminder(reminderId)取消系统提醒再删数据库记录。顺序反了或漏了 cancel会出现「数据库没任务、系统还提醒」的幽灵闹钟——这是提醒应用最常见的 bug。七、启动同步数据库 ↔ 系统提醒的一致性应用每次启动要校准「数据库任务」和「系统提醒」的一致性exportclassReminderSync{/** * 启动校准 * 1. 系统有提醒但库无任务 → 取消防幽灵 * 2. 库任务提醒已过期未完成 → 标记 MISSED * 3. 库任务无 reminderId → 重新注册。 */staticasyncsync(context:common.UIAbilityContext):Promisevoid{consttasksawaitTaskDao.queryAll(context);constvalidawaitReminderService.getValidReminders();constvalidIdsnewSet(valid.map((r)r.reminderId));for(consttaskoftasks){// 1. 到期未完成 → MISSEDif(task.statusTaskStatus.PENDINGtask.remindTypeRemindType.ONE_TIMEtask.remindTimeDate.now()){awaitTaskDao.updateStatus(context,task.id,TaskStatus.MISSED);continue;}// 2. 系统里没有对应提醒 → 重新注册异常恢复if(task.reminderId0!validIds.has(task.reminderId)){constnewIdawaitReminderService.publish(context,task);awaitTaskDao.updateReminderId(context,task.id,newId);}}}}这个校准函数是提醒应用的「体检」系统提醒可能被用户手动清空、被系统清理启动时对账才能保证「库里的任务永远有对应的系统提醒」。八、代码定位表代码块关注点改动入口module.json5 权限PUBLISH_AGENT_REMINDER提醒权限变更时改这里TaskDao任务表 CRUD增加任务字段时改建表 SQLReminderService.publish三种提醒构造调整提醒类型时改分支dailyTriggerInSeconds定时触发换算算错时间先看这里ReminderService.cancel取消提醒删除流程必调ReminderSync启动对账异常恢复策略调整时改这里九、本篇小结提醒服务层的骨架是「数据库存业务、系统存提醒、启动对账」任务的一切业务状态在 SQLite提醒的执行在 reminderAgentManager两者靠 reminderId 关联靠启动校准保证一致。提醒机制的核心是理解「系统代理」——提醒不依赖应用存活这既是能力也是责任删除任务必须取消提醒。关键记忆点单次用 CALENDAR、每天用 TIMER、每周用 CALENDARrepeatDaysOfWeekrepeatDaysOfWeek 的 1周日与 getDay() 不同wantAgent 是通知回应用的桥梁删除任务先 cancel 再删库。
返回列表