0.0.8:借服务卡片 30 分钟刷新驱动后台同步
用户反馈"基本不会定期同步,每次都要手动点进去才同步"。
诊断发现主因是「后台自动同步」默认关闭(workScheduler 2 小时兜底根本没注册),
且系统对普通应用后台频率有硬管控(活跃分组下限 2 小时)。
本提交新增一条**免权限、约 30 分钟一次**的定期同步路径:
利用服务卡片已有的定时刷新(form_config.json 的 updateDuration=1,**单位 30 分钟**,
系统每 30 分钟调用一次 onUpdateForm),在该回调里顺带跑一次后台同步。
改动:
1. AppSettings 新增 `card_sync_at`(上次卡片驱动同步时间戳,用于节流)。
2. BackgroundSyncRunner 新增 `runOnceThrottledByCard(context)`:
· 节流 CARD_MIN_GAP_MS = 25 分钟(略小于 30 分钟卡片周期,避免系统刷新漂移被误跳过);
· **先写时间戳再执行** → 防止同步期间卡片再次刷新导致并发重入;
· 复用现有 `runOnce(context,'cardTick')`(完整同步:入站对账→推送→拉取→镜像→刷新卡片与提醒);
· **永不抛错**(后台路径没有 UI 承担异常)。
3. EntryFormAbility.onUpdateForm:
· ① 先用**本地缓存**立刻刷卡片(毫秒级,不依赖网络);
· ② 再跑节流同步,完成后二次推卡片数据,让卡片显示刚同步回来的结果。
⚠️ 关键约束(官方 FormExtensionAbility 生命周期原文):
"FormExtensionAbility 进程不能常驻后台,生命周期回调中无法处理长时间的任务,
生命周期调度完成后会继续存在 **10 秒**……可能需要 10 秒以上才能完成的业务逻辑,
建议拉起主应用进行处理。"
⇒ 同步**可能被进程回收中断**,故:
· 设 **9 秒软超时**主动收手(干净放弃而非被硬杀在半途);
· 语义明确为"**尽力而为地提高后台同步频率**",不承诺 30 分钟必然成功;
· 中断安全:同步幂等(etag 增量比对 + dirty 标记),中断不损坏数据,
下个周期会重试;正常网络下增量同步通常数秒内完成,落在窗口内;
· **不采用"拉起主应用同步"** —— 那会每 30 分钟弹出一次界面,体验不可接受。
配额核算:卡片定时刷新官方上限为**每张卡片每天 50 次**;30 分钟一次 = 48 次/天,
**不超配额**。相比 workScheduler(受活跃分组压制到 ≥2 小时),频率提升约 4 倍。
验证:编译通过(BUILD SUCCESSFUL);节流逻辑离线用例通过
(从未同步/30 分钟/26 分钟/正好 25 分钟 → 执行;24/5/1 分钟 → 跳过)。
This commit is contained in:
@@ -20,6 +20,10 @@ export class AppSettings {
|
||||
private static readonly KEY_MANUAL_READONLY: string = 'manual_readonly_keys'; // 手动标记只读的 calKey
|
||||
private static readonly KEY_MUTED_REMINDER: string = 'muted_reminder_keys'; // 提醒静音的 calKey
|
||||
private static readonly KEY_REMINDER_TICK: string = 'reminder_tick'; // 应用内提醒上次检查时间戳
|
||||
// ⭐ 卡片刷新驱动的后台同步:上次执行时间戳(用于节流)。
|
||||
// 服务卡片每 30 分钟被系统刷新一次(form_config 的 updateDuration=1),
|
||||
// 借这个时机顺带跑一次后台同步 —— 得到"约 30 分钟一次"的定期同步。
|
||||
private static readonly KEY_CARD_SYNC_AT: string = 'card_sync_at';
|
||||
private static readonly KEY_FULL_REFETCH: string = 'full_refetch_done'; // 一次性全量重拉已完成
|
||||
private static readonly KEY_DEFAULT_VIEW: string = 'default_view'; // 打开 App 默认视图
|
||||
private static readonly KEY_DISPLAY_STYLE: string = 'display_style'; // 日程显示方式:'timeline' | 'list'
|
||||
@@ -323,6 +327,29 @@ export class AppSettings {
|
||||
}
|
||||
}
|
||||
|
||||
/** ⭐ 卡片刷新驱动的后台同步:上次执行时间戳(0 = 从未执行) */
|
||||
static async getCardSyncAt(context: common.Context): Promise<number> {
|
||||
try {
|
||||
const store: preferences.Preferences =
|
||||
await preferences.getPreferences(context, AppSettings.STORE);
|
||||
return await store.get(AppSettings.KEY_CARD_SYNC_AT, 0) as number;
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static async setCardSyncAt(context: common.Context, ts: number): Promise<void> {
|
||||
try {
|
||||
const store: preferences.Preferences =
|
||||
await preferences.getPreferences(context, AppSettings.STORE);
|
||||
await store.put(AppSettings.KEY_CARD_SYNC_AT, ts);
|
||||
await store.flush();
|
||||
} catch (err) {
|
||||
const e = err as BusinessError;
|
||||
console.error(`保存卡片同步时间失败: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否还需要一次性全量重拉:修复历史同步(REPORT 剥离 VALARM 时期)落库的残缺数据。
|
||||
* 全量重拉期间忽略 etag 复用,所有资源 GET 完整 ICS;成功完成后标记,恢复增量模式。
|
||||
|
||||
Reference in New Issue
Block a user