v73:桌面卡片刷新修复 + 首页日程/任务就地弹详情卡 + 一键导航

用户 2026-10-21 三条:
  「卡片里面的数据并不刷新……应用里面的日程已经有了,但卡片上的日程却并不刷新」
  「首页上的那个日程和任务,点击之后都应该像日程和任务里面的卡片一样弹出来,
    不要进入到日程里面……有地址的还可以直接一键导航」
  「任务也是一样,点击任务之后弹出来详情,可以标记它是不是完成了」

① 卡片不刷新(根因)
   卡片进程(FormExtensionAbility)与应用进程靠 preferences 传 formIds / 翻页偏移。
   SDK 明文禁止跨进程用 preferences,且 getPreferences() 在**进程内**缓存实例、
   同名再取不读盘 ⇒ 应用进程可能永久持有空注册表 ⇒ pushAll() 首行
   `if (list.length === 0) return` 直接返回 ⇒ 卡片只靠系统 30 分钟自刷。
   修:store() 每次先 removePreferencesFromCacheSync 再取;并写死单写者纪律
   (register/unregister/setOffset/clearOffset 只由卡片进程调,应用进程只读)。

② 首页点日程 → 弹 FbEventSheet(canEdit: false,首页不给编辑入口)
   有地址的行整行可点 + 右侧「➤ 导航」胶囊;「编辑日程」才跳日历模块
   (编辑是日历页的职责,不在首页复制第二份表单)。

③ 首页点任务 → 新的 FbTaskDetailSheet
   状态圆可点(勾完成)、标题带删除线、底部「标记为已完成/未完成」+「改一改」。
   状态**必须走 POST /tasks/<id>/status**(PATCH 会忽略 status)。

④ 一键导航 FbNav.ets:startAbility 隐式 Want(不是 openLink,那是 App Linking);
   canOpenLink 需 module.json5 声明 querySchemes(未声明恒 false 且不抛错);
   高德/百度/花瓣/腾讯按序探测,一个地图都没装则复制地点到剪贴板 + Toast。

⑤ 首页登记 FB_BACK(与其余 10 个模块一致),根容器 Stack(浮层是兄弟节点,
   不能当 Column 子项排,否则卡片被挤出屏幕)。

静态守卫 905/0、v73 反向验证 17/0、assembleHap 通过、已装机启动。
This commit is contained in:
2026-10-21 20:01:37 +08:00
parent 15bc3c8599
commit 48a0507455
7 changed files with 1282 additions and 32 deletions
+57
View File
@@ -16,6 +16,36 @@
* ⚠️ 偏移量做**夹取**(±366 天):`onFormEvent` 的 message 是外部输入,
* 连续点 ‹ 几百次也不该把它算成一个荒唐的年份(`new Date(y, m, d + 99999)`
* 在部分设备上会算出 Invalid Date,卡片直接白掉)。
*
* 🔴🔴 v73(用户 2026-10-21「卡片上的日程并不刷新」)—— **本文件的最大坑,别再踩**:
*
* 【症状】应用里日程已经改了/已经有了,桌面卡片上的日程**永远是旧的**;
* 偶尔连"刚添加的卡片"都不带数据。
*
* 【根因】这份状态是**跨进程**的(写的一方是 `FbFormAbility` 那个卡片进程,
* 读的一方是应用进程的 `FB_CARD_FORMS.pushAll`),而
* `@ohos.data.preferences` 的 SDK 文档**明确写着**:
* 「Preferences are **not thread-safe** and may cause file damage and
* data loss when used in **multi-process** scenarios.
* **Do not use preferences in multi-process scenarios.**」
* 比"数据可能损坏"更直接的问题是**实例缓存**:
* `getPreferences()` 把实例缓存在**进程内**,之后同名的调用
* **直接返回缓存、不再读盘**。
* ⇒ 应用进程只要在"桌面上还没有卡片"的时候读过一次 `fb_card_forms`,
* 它就拿住了一个**空注册表**的缓存实例,此后**永远**读到空串
* ⇒ `pushAll()` 第一行 `if (list.length === 0) return;` 直接返回
* ⇒ 卡片**一次都收不到应用推的数据**(只剩系统 30 分钟自己刷)。
* 偏移量也有同一个毛病:用户把卡翻到"明天",应用进程读到的还是缓存里的 0,
* 推过去的数据是今天的 ⇒ 卡片"翻着翻着自己跳回今天"。
*
* 【修法】① 读之前**显式作废本进程的实例缓存**
* (`removePreferencesFromCacheSync` + 重新 `getPreferences`),
* 保证读到的永远是磁盘上的最新值;
* ② 保持"**单写者**":本文件的 `register/unregister/setOffset/clearOffset`
* **只被 `FbFormAbility`(卡片进程)调用**,应用进程**只读不写**
* ⇒ 不会出现两个进程同时写同一个文件(那才是文档警告的"损坏"场景)。
* ⚠️ 这条是硬约束:**以后不要让应用侧去写 formIds / 偏移量**。
* 要写也得走"给卡片进程发消息、让它来写"。
*/
import { common } from '@kit.AbilityKit';
@@ -40,7 +70,34 @@ const SEP_ITEM: string = ';';
const SEP_KV: string = '=';
class FbCardFormsImpl {
/**
* 取 preferences 实例 —— **每次都作废进程内缓存再取**。
*
* 🔴🔴 v73 的核心修复(详见文件头"最大坑"那段):
* `getPreferences()` 会把实例缓存在进程内,之后同名调用**不再读盘**
* ⇒ 卡片进程写进去的 formIds / 偏移量,应用进程**永远看不到**
* ⇒ 日程改了、桌面卡片不刷新。
* ⇒ 这里每次先用 `removePreferencesFromCacheSync` 把本进程的缓存摘掉,
* 再 `getPreferences` 让系统**重新从磁盘读**。
*
* ⚠️ `removePreferencesFromCacheSync` 是 **API 10+**(本工程 compatibleSdk 24,够用);
* 它**不会删数据**,只是把内存里的那份实例从缓存表里摘出来(名字里有
* "remove" 很容易被误读成"删文件",其实是 `fromCache`)。
* ⚠️ 摘缓存后,**旧实例不能再用**(SDK 原文:"Avoid using a removed Preferences
* instance to perform data operations, which may cause data inconsistency")
* ⇒ 所以本类**不缓存 `Preferences` 实例**,需要时现取。
* ⚠️ 每次读写都重开有开销(~几毫秒),但本类的调用频率极低
* (加卡/删卡/翻页/应用内改完日程推一次),完全不值得为此引入缓存。
* 相比之下,"读到旧数据"的代价是**整个卡片功能失效**。
*/
private async store(ctx: common.Context): Promise<preferences.Preferences> {
try {
preferences.removePreferencesFromCacheSync(ctx, STORE_NAME);
} catch (err) {
// 摘缓存失败不致命:下面照样能取到(可能拿到缓存的旧实例),先记一条
const e: BusinessError = err as BusinessError;
hilog.warn(DOMAIN, TAG, '作废卡片状态缓存失败(继续用缓存实例):%{public}s', e.message);
}
return preferences.getPreferences(ctx, STORE_NAME);
}