查看: 132|回复: 0

鸿蒙 Calendar Kit 里程碑写入与后台同步避坑

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
背景:为什么要把里程碑写进系统日历

“轻规划”(AeroPlan)的目标是把大目标拆解为可执行里程碑。原文指出,计划流产的核心痛点之一是“做好了规划,但过几天就忘了主动去看”。如果待办只锁在 App 内部,用户没有打开习惯,通知又可能因进程被查杀、保活受限而触达失败,规划系统就容易瘫痪。HarmonyOS NEXT 的 Calendar Kit 提供了直接操纵系统级日程的底座。把拆解出的行动和习惯日程写入系统日历后,即使应用进程被终止,系统日历通知仍由系统托管按时提醒;桌面日历元服务小组件可渲染当天关键任务;小艺等系统智能语音助理也能读取日程,提供出行与规划提醒。

一、同步管线:业务层、桥接器和系统日历服务

原文把链路拆成四层:应用业务层负责用 AI 拆解大目标,计算开始时间与持续时间;数据桥接器 CalendarHelper 与 CalendarSyncMutex 负责 Context 兼容和并发控制;Calendar Kit API 通过底层 IPC 写入系统内置 SQLite;系统级呈现层由设备系统直接渲染,不受应用生命周期约束。对开发者而言,这意味着写入操作要异步执行,避免阻塞主线程,同时要处理好前后台 Context 与并发冲突。

二、权限申请与默认日历账户获取

日历读写属于敏感高危操作。首先在 entry/src/main/module.json5 的 requestPermissions 中静态声明 ohos.permission.READ_CALENDAR 和 ohos.permission.WRITE_CALENDAR。每个权限的 reason 字段必须在 resources/base/element/string.json 中配置清晰、明确的中文理由,否则华为应用市场上架审核可能因“权限申请理由描述不清”被拒。usedScene 中可配置 abilities 为 EntryAbility、when 为 inuse。

运行时再通过 abilityAccessCtrl 动态申请,授权结果中 code 为 0 表示成功;随后调用 calendarManager.getCalendarManager(context) 获取管理器,并通过 getDefaultCalendar() 绑定系统默认日历账户。核心初始化逻辑如下:
  1. import { calendarManager } from '@kit.CalendarKit';
  2. import { abilityAccessCtrl, common } from '@kit.AbilityKit';
  3. import { BusinessError } from '@kit.BasicServicesKit';
  4. export class CalendarHelper {
  5.   private calendarMgr: calendarManager.CalendarManager | null = null;
  6.   private defaultCalendar: calendarManager.Calendar | null = null;
  7.   public async init(context: common.UIAbilityContext): Promise<boolean> {
  8.     const atManager = abilityAccessCtrl.createAtManager();
  9.     try {
  10.       const grantResult = await atManager.requestPermissionsFromUser(context, [
  11.         'ohos.permission.READ_CALENDAR',
  12.         'ohos.permission.WRITE_CALENDAR'
  13.       ]);
  14.       const isGranted = grantResult.authResults.every(res => res === 0);
  15.       if (!isGranted) {
  16.         console.warn('CalendarHelper', 'Calendar permissions denied by user.');
  17.         return false;
  18.       }
  19.       this.calendarMgr = calendarManager.getCalendarManager(context);
  20.       this.defaultCalendar = await this.calendarMgr.getDefaultCalendar();
  21.       return true;
  22.     } catch (err) {
  23.       console.error('CalendarHelper', `Init calendar manager failed: ${(err as BusinessError).message}`);
  24.       return false;
  25.     }
  26.   }
  27. }
复制代码

三、写入带强提醒的里程碑 Event

写入时最关键的是时间窗和提醒规则。原文使用 startTime、endTime 描述日程区间,把 type 设为 calendarManager.EventType.IMPORTANT,让系统用醒目样式并赋予更高提醒优先级;remindTimes: [15] 表示事件开始前 15 分钟发送系统级通知。标题可加应用前缀,如 [轻规划]。示例:
  1. public async addMilestoneSchedule(
  2.   title: string,
  3.   description: string,
  4.   startTimeMs: number,
  5.   durationMinutes: number
  6. ): Promise<void> {
  7.   if (!this.defaultCalendar) {
  8.     throw new Error('Calendar Helper not initialized or default calendar missing');
  9.   }
  10.   const endTimeMs = startTimeMs + (durationMinutes * 60 * 1000);
  11.   const event: calendarManager.Event = {
  12.     title: `[轻规划] ${title}`,
  13.     description: description,
  14.     type: calendarManager.EventType.IMPORTANT,
  15.     startTime: startTimeMs,
  16.     endTime: endTimeMs,
  17.     remindTimes: [15]
  18.   };
  19.   try {
  20.     await this.defaultCalendar.addEvent(event);
  21.   } catch (error) {
  22.     console.error('CalendarHelper', `Insert event failed: ${(error as BusinessError).message}`);
  23.     throw error;
  24.   }
  25. }
复制代码

四、后台静默 AutoSync 的两个坑

轻规划提供 AutoSync 自动后台同步:当后台 AI 服务或小艺建议识别到日程冲突并重排时间线时,应用需静默更新日历。原文在这里踩过两个典型问题。

坑 1:非 UI 上下文调用 getCalendarManager 报 401

在 WorkScheduler 长时任务或 ExtensionAbility 中直接调用 calendarManager.getCalendarManager(context),如果传入前台 UIAbilityContext 或 null 句柄,系统会抛出 BusinessError 401: Invalid context。原因是后台任务生命周期与宿主 Ability 独立,没有前台界面。解决方式是使用 context.getApplicationContext() 获取全局上下文,再获取日历管理器:
  1. const appContext = context.getApplicationContext();
  2. this.calendarMgr = calendarManager.getCalendarManager(appContext);
复制代码

坑 2:前后台并发写导致 relationalStore 锁死

系统日历底层是 SQLite 关系型数据库,并通过 IPC 提供服务。后台定时静默同步可能在写入多条日程,前台用户也可能同时手动点击同步,两端并发写同一张表会形成写竞争,触发底层 relationalStore 锁死,抛出数据库繁忙错误,甚至让同步模块崩溃。原文的做法是在应用内加一层内存级悲观互斥锁 CalendarSyncMutex:任何日历同步读写都走 executeSync,若已有同步事务在执行,就直接丢弃冗余请求;无论成功失败都在 finally 中释放锁,避免死锁。
  1. export class CalendarSyncMutex {
  2.   private static isSyncing = false;
  3.   public static async executeSync(action: () => Promise<void>): Promise<void> {
  4.     if (this.isSyncing) {
  5.       console.warn('CalendarSyncMutex', 'An active calendar sync transaction is running. Request discarded.');
  6.       return;
  7.     }
  8.     this.isSyncing = true;
  9.     try {
  10.       await action();
  11.     } catch (error) {
  12.       console.error('CalendarSyncMutex', `Error occurred during sync transaction: ${(error as Error).message}`);
  13.     } finally {
  14.       this.isSyncing = false;
  15.     }
  16.   }
  17. }
复制代码

业务层调用时,用 CalendarSyncMutex.executeSync 包住 addMilestoneSchedule 等写入或更新操作,就可以把前后台同步请求串行化。

五、小结

打通 Calendar Kit 后,应用不必完全依赖自身进程存活来提醒用户。原文总结的关键点是:静态与动态权限双保险、后台使用全局 ApplicationContext 初始化、用内存级悲观互斥锁治理并发。这样既能提高里程碑触达率,也能规避后台静默同步中的 Context 失效和数据库并发死锁。原文还预告下一篇将进入 Multimodal Awareness Kit,讨论智感握姿感知、悬浮操作面板与输入框主手热区自适应切换。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-12 10:15 , Processed in 0.032112 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部