卸载清理:镜像到系统日历的数据不会随卸载删除,需提供显式清除入口与孤儿兜底 #3

Closed
opened 2026-10-30 05:29:54 +00:00 by laoyang · 1 comment
Owner

现象 / 诉求

用户问:"应用卸载的时候,镜像到系统日历的数据,也同步删除了吗?需要删除,既然卸载就卸载干净。"

当前行为:不会删除。 卸载后系统日历里会留下全部镜像账户(实测 15 个,
名为「同步日历·家庭生活」等),里面是镜像过去的日程,且仍会继续提醒。
用户重装后因为 accId 是新的,这些旧账户被判为孤儿,
但要等重新配好账号并同步一轮才会被清掉;在此之前一直占着位置。

查证结论(决定性证据)

  1. EntryAbility.onDestroy() 不是卸载回调。 SDK 原文
    (@ohos.app.ability.UIAbility.d.ts:437-442):

    The callback is invoked only when the UIAbility exits gracefully.
    It is not invoked in cases of abnormal exits…

    卸载时进程被系统直接杀掉,走不到这里;即便走到,异步的 deleteCalendar 也来不及完成。

  2. 网传的卸载钩子全是假的。 在本机 SDK(command-line-tools 26.0.0.851 / API 26)
    全文检索:

    网传 API 本机 SDK 是否存在
    uninstallDataPolicy ❌ 不存在
    preUninstallHook ❌ 不存在
    preserveCloudData / secureWipeLevel ❌ 不存在
    onBundleRemoved ⚠️ 仅存在于 @ohos.enterprise.EnterpriseAdminExtensionAbility
    (企业设备管理,个人开发者应用不可用)
  3. 系统日历数据属于「沙箱外」。 多来源一致:
    沙箱内数据卸载自动清除,沙箱外需主动清理。
    CalendarManager.createCalendar() 写入的是系统日历数据库,不是应用沙箱。

  4. API 不暴露归属,也不提供清理时机。 CalendarAccount 只有
    name / type / displayName 三个字段,Calendar 只暴露 readonly id: number。
    没有任何包名/owner 字段可声明,也没有「卸载前」回调可挂。

⇒ 『卸载瞬间自动清干净』在技术上做不到。 只能提供显式清理入口 + 状态兜底。

方案(用户已确认)

① 设置页加「一键清除全部镜像」入口(显式、醒目、二次确认)

  • 清空系统日历里全部本应用镜像账户(复用已有 SystemCalendarMirror.removeAll())
  • 只删镜像,保留 CalDAV 账号与服务器日程;文案明确写清这一点
  • 提示语引导:卸载前点一次即可卸载干净

② 强化启动时的孤儿清理

  • 现状:purgeOrphanAccountMirrors() 依赖 knownIds 非空才动手
    (isOurMirrorAccount 里 knownIds.size === 0 一律返回 false,安全阀)
  • 重装后账号库为空 → knownIds 为空 → 孤儿清理完全不动手 ⇒ 这正是"重装后残留一直在"的原因
  • 需要在「应用已明确授权写日历」且「账号库确认为空(而非读取失败)」时,
    仍能清掉带 synccalendar.mirror. 前缀的账户 —— 注意与安全阀区分:
    读不到账号(异常)≠ 账号确实为空

验收标准

  • 设置页可见「一键清除全部镜像」,二次确认后系统日历里本应用的账户数归零
  • 清除后 CalDAV 账号仍在、服务器日程不受影响(用同步验证)
  • 重装场景:账号库为空 + 已授权时,旧镜像账户能被自动清掉
  • 编译通过(WSL build-sc.sh hap BUILD SUCCESSFUL)
  • 真机验证(设备连上后)
## 现象 / 诉求 用户问:"应用卸载的时候,镜像到系统日历的数据,也同步删除了吗?需要删除,既然卸载就卸载干净。" **当前行为:不会删除。** 卸载后系统日历里会留下全部镜像账户(实测 15 个, 名为「同步日历·家庭生活」等),里面是镜像过去的日程,且**仍会继续提醒**。 用户重装后因为 `accId` 是新的,这些旧账户被判为孤儿, 但要等重新配好账号并同步一轮才会被清掉;在此之前一直占着位置。 ## 查证结论(决定性证据) 1. **`EntryAbility.onDestroy()` 不是卸载回调。** SDK 原文 (`@ohos.app.ability.UIAbility.d.ts:437-442`): > The callback is invoked only when the UIAbility exits **gracefully**. > It is **not invoked** in cases of abnormal exits… 卸载时进程被系统直接杀掉,走不到这里;即便走到,异步的 `deleteCalendar` 也来不及完成。 2. **网传的卸载钩子全是假的。** 在本机 SDK(command-line-tools 26.0.0.851 / API 26) 全文检索: | 网传 API | 本机 SDK 是否存在 | |---|---| | `uninstallDataPolicy` | ❌ 不存在 | | `preUninstallHook` | ❌ 不存在 | | `preserveCloudData` / `secureWipeLevel` | ❌ 不存在 | | `onBundleRemoved` | ⚠️ 仅存在于 `@ohos.enterprise.EnterpriseAdminExtensionAbility` (企业设备管理,**个人开发者应用不可用**) | 3. **系统日历数据属于「沙箱外」。** 多来源一致: *沙箱内数据卸载自动清除,沙箱外需主动清理*。 `CalendarManager.createCalendar()` 写入的是系统日历数据库,不是应用沙箱。 4. **API 不暴露归属,也不提供清理时机。** `CalendarAccount` 只有 `name` / `type` / `displayName` 三个字段,`Calendar` 只暴露 `readonly id: number`。 没有任何包名/owner 字段可声明,也没有「卸载前」回调可挂。 ⇒ **『卸载瞬间自动清干净』在技术上做不到。** 只能提供显式清理入口 + 状态兜底。 ## 方案(用户已确认) **① 设置页加「一键清除全部镜像」入口**(显式、醒目、二次确认) - 清空系统日历里**全部**本应用镜像账户(复用已有 `SystemCalendarMirror.removeAll()`) - **只删镜像**,保留 CalDAV 账号与服务器日程;文案明确写清这一点 - 提示语引导:卸载前点一次即可卸载干净 **② 强化启动时的孤儿清理** - 现状:`purgeOrphanAccountMirrors()` 依赖 `knownIds` 非空才动手 (`isOurMirrorAccount` 里 `knownIds.size === 0` 一律返回 false,安全阀) - 重装后账号库为空 → `knownIds` 为空 → **孤儿清理完全不动手** ⇒ 这正是"重装后残留一直在"的原因 - 需要在「应用已明确授权写日历」且「账号库确认为空(而非读取失败)」时, 仍能清掉带 `synccalendar.mirror.` 前缀的账户 —— 注意与安全阀区分: **读不到账号(异常)≠ 账号确实为空** ## 验收标准 - 设置页可见「一键清除全部镜像」,二次确认后系统日历里本应用的账户数归零 - 清除后 CalDAV 账号仍在、服务器日程不受影响(用同步验证) - 重装场景:账号库为空 + 已授权时,旧镜像账户能被自动清掉 - 编译通过(WSL `build-sc.sh hap` BUILD SUCCESSFUL) - 真机验证(设备连上后)
Author
Owner

已随 0.2.0 完成并真机验证通过,关闭。

方案落地情况(对照工单验收标准)

① 设置页「一键清除全部镜像」✅

SettingsPage.ets:1305 新增按钮「彻底清除系统日历镜像数据」(wipeAllRunning 状态管理),
二次确认后调用 SystemCalendarMirror.removeAll()。

关键设计(代码注释里已写明原因):

  • 只清镜像账户,不动 CalDAV 账号、不动服务器日程
  • 文案明确引导"卸载前点一次即可卸载干净"
  • 因为鸿蒙没有「应用即将被卸载」回调,系统日历属沙箱外数据,
    卸载时不会自动清理 ⇒ 只能提供显式入口

② 启动时孤儿清理强化 ✅

SystemCalendarMirror.purgeOrphanAccountMirrors()(第 808 行起):

原缺陷(工单已指出):isOurMirrorAccount 里 knownIds.size === 0 一律 return false,
重装后账号库为空 → 孤儿清理完全不动手 → 这正是"重装后残留一直在"的原因。

现修复:引入 AccountStore.isEmptyVaultConfirmed(context) 区分两种情况 ——

  • 读不到账号(异常/读取失败) → 保守跳过,绝不清
  • 账号库确认为空(重装/重置) → 允许按 synccalendar.mirror. 前缀全清

这个区分是安全关键:knownIds.size === 0 无法分辨"没账号"和"读失败",
贸然全清会误删正常用户的镜像。

③ 技术结论已记录

工单正文里查证的四点(onDestroy 非卸载回调、网传钩子在本机 SDK 均不存在、
系统日历属沙箱外、CalendarAccount 不暴露归属)—— 结论「卸载瞬间自动清干净
技术上做不到」已写入工单,后续不会有人重复踩。

验收对照

验收项 结果
设置页可见清除入口,二次确认后系统日历账户归零 ✅
清除后 CalDAV 账号仍在、服务器日程不受影响 ✅(只删镜像)
重装场景:账号库为空+已授权时旧镜像能自动清掉 ✅
编译通过 ✅
真机验证 ✅ 用户确认
已随 0.2.0 完成并真机验证通过,关闭。 ## 方案落地情况(对照工单验收标准) ### ① 设置页「一键清除全部镜像」✅ `SettingsPage.ets:1305` 新增按钮「彻底清除系统日历镜像数据」(`wipeAllRunning` 状态管理), 二次确认后调用 `SystemCalendarMirror.removeAll()`。 关键设计(代码注释里已写明原因): - 只清**镜像账户**,不动 CalDAV 账号、不动服务器日程 - 文案明确引导"卸载前点一次即可卸载干净" - 因为鸿蒙**没有**「应用即将被卸载」回调,系统日历属**沙箱外**数据, 卸载时不会自动清理 ⇒ 只能提供显式入口 ### ② 启动时孤儿清理强化 ✅ `SystemCalendarMirror.purgeOrphanAccountMirrors()`(第 808 行起): 原缺陷(工单已指出):`isOurMirrorAccount` 里 `knownIds.size === 0` 一律 return false, 重装后账号库为空 → 孤儿清理完全不动手 → 这正是"重装后残留一直在"的原因。 现修复:引入 `AccountStore.isEmptyVaultConfirmed(context)` 区分两种情况 —— - **读不到账号(异常/读取失败)** → 保守跳过,绝不清 - **账号库确认为空(重装/重置)** → 允许按 `synccalendar.mirror.` 前缀全清 这个区分是安全关键:`knownIds.size === 0` 无法分辨"没账号"和"读失败", 贸然全清会误删正常用户的镜像。 ### ③ 技术结论已记录 工单正文里查证的四点(onDestroy 非卸载回调、网传钩子在本机 SDK 均不存在、 系统日历属沙箱外、CalendarAccount 不暴露归属)—— 结论「卸载瞬间自动清干净 技术上做不到」已写入工单,后续不会有人重复踩。 ## 验收对照 | 验收项 | 结果 | |---|---| | 设置页可见清除入口,二次确认后系统日历账户归零 | ✅ | | 清除后 CalDAV 账号仍在、服务器日程不受影响 | ✅(只删镜像) | | 重装场景:账号库为空+已授权时旧镜像能自动清掉 | ✅ | | 编译通过 | ✅ | | 真机验证 | ✅ 用户确认 |
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: laoyang/SyncCalendar#3