HarmonyOs应用《重要日》开发第27篇 - 前台提醒检查机制设计
本文深入解析 ImportantDays 项目中的前台提醒检查机制包括为什么需要在 onForeground 中检查提醒、checkAndSendReminders 的完整流程、与 onCreate 中检查的区别、重复通知的处理策略以及这种机制的优势与局限。一、为什么需要前台检查鸿蒙的后台限制HarmonyOS 对后台应用有严格限制后台应用无法主动发送通知后台应用的网络和计算能力受限后台应用可能被系统杀死因此ImportantDays 采用前台检查策略应用每次回到前台时检查是否有需要提醒的重要日。典型场景时间线 08:00 用户打开应用设置重要日3天后提醒 08:05 用户退出应用 ...应用在后台 11:00 重要日进入提醒范围假设提前3天 ...应用仍在后台无法发通知 14:00 用户重新打开应用 → onForeground 触发 → checkAndSendReminders() → 发送通知如果没有前台检查用户在 11:00 到 14:00 之间不会收到任何提醒。二、检查的三个触发点1. onCreate冷启动asynconCreate(want:Want,launchParam:AbilityConstant.LaunchParam):Promisevoid{PreferenceUtil.init(this.context);ImportantDayManager.getInstance().init();// init 内部调用 checkAndSendRemindersconsthasPermissionawaitNotificationUtil.checkPermission();if(hasPermission){awaitImportantDayManager.getInstance().checkAndSendReminders();// 再次检查}}2. init()Manager 初始化init():void{if(this.initialized)return;this.allDaysPreferenceUtil.loadImportantDays();this.rebuildDayMap();this.initializedtrue;this.checkAndSendReminders();// 初始化时检查}3. onForeground回到前台onForeground():void{ImportantDayManager.getInstance().checkAndSendReminders();}三个触发点的关系冷启动 onCreate → init() → checkAndSendReminders (第1次) → checkPermission → checkAndSendReminders (第2次) → onForeground → checkAndSendReminders (第3次) 热启动从后台恢复 onForeground → checkAndSendReminders (唯一一次) 通知点击启动 onCreate → init() → checkAndSendReminders → onForeground → checkAndSendReminders为什么冷启动会检查三次init()中的检查确保数据加载后立即检查onCreate中的检查确保权限确认后再次检查onForeground中的检查系统标准回调这三次检查是冗余的checkAndSendReminders内部有权限检查但确保了可靠性。三、checkAndSendReminders 完整流程asynccheckAndSendReminders():Promisevoid{// 第一步获取需要提醒的重要日constneedReminderthis.getDaysNeedReminder();// 第二步检查通知权限consthasPermissionawaitNotificationUtil.checkPermission();if(!hasPermission){return;// 无权限则退出}// 第三步逐个发送通知for(constdayofneedReminder){consttitleday.isToday()?今天${day.title}:即将到来${day.title};constcontentday.getDisplayText();awaitNotificationUtil.publishBasic(title,content,this.getNotificationId(day.dayID));}}流程图checkAndSendReminders() ↓ getDaysNeedReminder() ↓ 过滤reminderEnabled diff 0 diff reminderDaysBefore ↓ needReminder [...] ↓ needReminder 为空 ├── 是 → 结束 └── 否 ↓ checkPermission() ↓ 有权限 ├── 否 → 结束 └── 是 ↓ 遍历 needReminder ↓ 对每个 day ├── 计算 title今天/即将到来 ├── 计算 contentgetDisplayText └── publishBasic(title, content, notificationId) ↓ 完成四、getDaysNeedReminder 的筛选逻辑getDaysNeedReminder():ImportantDay[]{returnthis.allDays.filter(dd.shouldRemind());}shouldRemind 的条件shouldRemind():boolean{if(!this.reminderEnabled)returnfalse;// 条件1启用提醒constdiffthis.getDaysDiff();returndiff0diffthis.reminderDaysBefore;// 条件2在范围内}筛选示例重要日列表 1. 妈妈生日reminderEnabledtrue, reminderDaysBefore3, diff2→ 需要 2. 结婚纪念日reminderEnabledtrue, reminderDaysBefore7, diff10→ 不需要超出范围 3. 旅行计划reminderEnabledfalse, diff1→ 不需要未启用 4. 考试日期reminderEnabledtrue, reminderDaysBefore1, diff-2→ 不需要已过期 5. 今天会议reminderEnabledtrue, reminderDaysBefore1, diff0→ 需要 needReminder [妈妈生日, 今天会议]五、通知标题策略consttitleday.isToday()?今天${day.title}:即将到来${day.title};constcontentday.getDisplayText();标题分类场景标题格式示例今天今天${title}“今天妈妈生日”即将到来即将到来${title}“即将到来妈妈生日”内容contentday.getDisplayText();// 输出还有 2 天 / 就是今天 / 已过 3 天六、重复通知的处理问题每次onForeground都会发送通知可能导致同一重要日被多次通知。解决方案稳定的通知 IDprivategetNotificationId(dayID:string):number{lethash0;for(leti0;idayID.length;i){hash((hash5)-hash)dayID.charCodeAt(i);hash|0;}returnMath.abs(hash)%100000ImportantDayManager.notificationIdBase;}ID 稳定性同一个dayID总是生成相同的通知 ID。当使用相同 ID 发布通知时新通知会覆盖旧通知。覆盖行为第一次 onForeground: → publishBasic(即将到来妈妈生日, 还有 2 天, 10234) → 通知栏显示通知 第二次 onForeground2小时后: → publishBasic(即将到来妈妈生日, 还有 2 天, 10234) → 同 ID → 覆盖旧通知不会出现两条跨天场景Day 1 (diff2): → 通知内容还有 2 天 Day 2 (diff1): → 通知内容还有 1 天覆盖前一天的由于getDaysDiff()基于当前时间计算不同日期的通知内容会自动更新。七、与 scheduleReminder 的对比scheduleReminder单个提醒privateasyncscheduleReminder(day:ImportantDay):Promisevoid{constdiffday.getDaysDiff();if(diff0)return;if(diffday.reminderDaysBefore){consttitleday.isToday()?今天${day.title}:即将到来${day.title};constcontentday.getDisplayText();awaitNotificationUtil.publishBasic(title,content,this.getNotificationId(day.dayID));}}调用时机对比方法调用时机场景scheduleReminder添加/更新重要日后用户刚设置了提醒checkAndSendReminders启动/回前台定期检查所有重要日逻辑对比特性scheduleRemindercheckAndSendReminders检查单个是否检查所有检查权限否是调用频率低用户操作时高每次回前台scheduleReminder不检查权限是因为它通常在用户明确设置提醒后调用此时用户应该已授权。八、权限检查的时序应用启动 ↓ onCreate ↓ init() → checkAndSendReminders() ↓ checkPermission() → false首次使用未授权 ↓ return不发通知 ↓ checkPermission() → false ↓ 跳过 checkAndSendReminders ↓ 用户进入我的页面 ↓ 打开通知开关 → requestPermission() → 授权成功 ↓ onForeground下次回前台时 ↓ checkAndSendReminders() ↓ checkPermission() → true ↓ 发送通知九、机制的优势与局限优势简单可靠不需要后台任务或定时器零额外资源不消耗后台 CPU 和电量实时性好用户打开应用立即检查无权限要求不需要特殊后台权限局限依赖用户打开应用如果用户长时间不打开应用无法发送提醒非实时提醒延迟到用户打开应用时无精确定时无法在特定时间点如早上8点发送通知改进方向如果需要精确定时提醒可以使用鸿蒙的后台任务// 概念性改进需要后台任务权限import{backgroundTaskManager}fromkit.BackgroundTasksKit;// 注册延迟提醒任务backgroundTaskManager.startBackgroundRunning(context,...)但这需要额外的权限申请和更复杂的实现。十、总结前台提醒检查机制的设计体现了以下原则务实选择在后台限制下选择最简单可行的方案三重保障onCreate init onForeground 确保不遗漏权限先行每次检查前确认权限防重复稳定通知 ID 实现覆盖而非追加内容自更新基于当前时间计算内容通知随时间推移自动更新这套机制虽然简单但在不需要精确定时的场景下完全够用是鸿蒙应用通知提醒的务实之选。