HarmonyOS APP开发中的应用卸载:卸载监听、数据清理与安全考量全指南

HarmonyOS APP开发中的应用卸载:卸载监听、数据清理与安全考量全指南

📌 核心要点:掌握 HarmonyOS 应用卸载的监听与回调机制,实现卸载前数据清理、卸载后数据保留(重装恢复),以及卸载场景下的安全防护策略。


一、背景与动机

你有没有这样的经历:卸载一个 App 后重新安装,发现登录态没了、设置全丢了、之前买的东西找不回来了?用户对此的感受是------"这 App 不靠谱"。而另一边,有些 App 卸载后,你的个人信息还残留在设备上,这又是另一种"不靠谱"------安全隐患。

应用卸载,看似只是用户点一下"确认卸载"就结束了,但对开发者来说,这是一场与时间的赛跑。你需要在极短的时间内完成数据清理、状态同步、安全擦除等一系列操作。更复杂的是,HarmonyOS 的分布式数据可能在多个设备上存在副本,卸载时是否要同步清理?用户卸载后重装,哪些数据应该保留?

这些问题不是"锦上添花",而是关乎用户体验和数据安全的"必答题"。


二、核心原理

2.1 应用卸载流程

#mermaid-svg-rX2f5nlm47SDkiPl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rX2f5nlm47SDkiPl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rX2f5nlm47SDkiPl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rX2f5nlm47SDkiPl .error-icon{fill:#552222;}#mermaid-svg-rX2f5nlm47SDkiPl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rX2f5nlm47SDkiPl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rX2f5nlm47SDkiPl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rX2f5nlm47SDkiPl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rX2f5nlm47SDkiPl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rX2f5nlm47SDkiPl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rX2f5nlm47SDkiPl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rX2f5nlm47SDkiPl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rX2f5nlm47SDkiPl .marker.cross{stroke:#333333;}#mermaid-svg-rX2f5nlm47SDkiPl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rX2f5nlm47SDkiPl p{margin:0;}#mermaid-svg-rX2f5nlm47SDkiPl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rX2f5nlm47SDkiPl .cluster-label text{fill:#333;}#mermaid-svg-rX2f5nlm47SDkiPl .cluster-label span{color:#333;}#mermaid-svg-rX2f5nlm47SDkiPl .cluster-label span p{background-color:transparent;}#mermaid-svg-rX2f5nlm47SDkiPl .label text,#mermaid-svg-rX2f5nlm47SDkiPl span{fill:#333;color:#333;}#mermaid-svg-rX2f5nlm47SDkiPl .node rect,#mermaid-svg-rX2f5nlm47SDkiPl .node circle,#mermaid-svg-rX2f5nlm47SDkiPl .node ellipse,#mermaid-svg-rX2f5nlm47SDkiPl .node polygon,#mermaid-svg-rX2f5nlm47SDkiPl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rX2f5nlm47SDkiPl .rough-node .label text,#mermaid-svg-rX2f5nlm47SDkiPl .node .label text,#mermaid-svg-rX2f5nlm47SDkiPl .image-shape .label,#mermaid-svg-rX2f5nlm47SDkiPl .icon-shape .label{text-anchor:middle;}#mermaid-svg-rX2f5nlm47SDkiPl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rX2f5nlm47SDkiPl .rough-node .label,#mermaid-svg-rX2f5nlm47SDkiPl .node .label,#mermaid-svg-rX2f5nlm47SDkiPl .image-shape .label,#mermaid-svg-rX2f5nlm47SDkiPl .icon-shape .label{text-align:center;}#mermaid-svg-rX2f5nlm47SDkiPl .node.clickable{cursor:pointer;}#mermaid-svg-rX2f5nlm47SDkiPl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rX2f5nlm47SDkiPl .arrowheadPath{fill:#333333;}#mermaid-svg-rX2f5nlm47SDkiPl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rX2f5nlm47SDkiPl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rX2f5nlm47SDkiPl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rX2f5nlm47SDkiPl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rX2f5nlm47SDkiPl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rX2f5nlm47SDkiPl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rX2f5nlm47SDkiPl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rX2f5nlm47SDkiPl .cluster text{fill:#333;}#mermaid-svg-rX2f5nlm47SDkiPl .cluster span{color:#333;}#mermaid-svg-rX2f5nlm47SDkiPl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rX2f5nlm47SDkiPl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rX2f5nlm47SDkiPl rect.text{fill:none;stroke-width:0;}#mermaid-svg-rX2f5nlm47SDkiPl .icon-shape,#mermaid-svg-rX2f5nlm47SDkiPl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rX2f5nlm47SDkiPl .icon-shape p,#mermaid-svg-rX2f5nlm47SDkiPl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rX2f5nlm47SDkiPl .icon-shape .label rect,#mermaid-svg-rX2f5nlm47SDkiPl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rX2f5nlm47SDkiPl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rX2f5nlm47SDkiPl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rX2f5nlm47SDkiPl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-rX2f5nlm47SDkiPl .primary>*{fill:#4CAF50!important;stroke:#388E3C!important;color:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .primary span{fill:#4CAF50!important;stroke:#388E3C!important;color:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .primary tspan{fill:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .warning>*{fill:#FF9800!important;stroke:#F57C00!important;color:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .warning span{fill:#FF9800!important;stroke:#F57C00!important;color:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .warning tspan{fill:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .error>*{fill:#F44336!important;stroke:#D32F2F!important;color:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .error span{fill:#F44336!important;stroke:#D32F2F!important;color:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .error tspan{fill:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .info>*{fill:#2196F3!important;stroke:#1976D2!important;color:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .info span{fill:#2196F3!important;stroke:#1976D2!important;color:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .info tspan{fill:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .purple>*{fill:#9C27B0!important;stroke:#7B1FA2!important;color:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .purple span{fill:#9C27B0!important;stroke:#7B1FA2!important;color:#fff!important;}#mermaid-svg-rX2f5nlm47SDkiPl .purple tspan{fill:#fff!important;} 取消
确认
保留
清除
时间窗口有限
用户点击卸载
系统弹出确认对话框
用户确认
卸载取消
系统发送卸载广播
触发 onDisconnect 回调
执行数据清理逻辑
停止应用进程
删除应用文件
是否保留数据
保留应用沙箱外的数据
清除所有应用数据
卸载完成

2.2 卸载数据清理范围

#mermaid-svg-jz76Pv0hdIeLsKXQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jz76Pv0hdIeLsKXQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jz76Pv0hdIeLsKXQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jz76Pv0hdIeLsKXQ .error-icon{fill:#552222;}#mermaid-svg-jz76Pv0hdIeLsKXQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jz76Pv0hdIeLsKXQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jz76Pv0hdIeLsKXQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jz76Pv0hdIeLsKXQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jz76Pv0hdIeLsKXQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jz76Pv0hdIeLsKXQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jz76Pv0hdIeLsKXQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jz76Pv0hdIeLsKXQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jz76Pv0hdIeLsKXQ .marker.cross{stroke:#333333;}#mermaid-svg-jz76Pv0hdIeLsKXQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jz76Pv0hdIeLsKXQ p{margin:0;}#mermaid-svg-jz76Pv0hdIeLsKXQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jz76Pv0hdIeLsKXQ .cluster-label text{fill:#333;}#mermaid-svg-jz76Pv0hdIeLsKXQ .cluster-label span{color:#333;}#mermaid-svg-jz76Pv0hdIeLsKXQ .cluster-label span p{background-color:transparent;}#mermaid-svg-jz76Pv0hdIeLsKXQ .label text,#mermaid-svg-jz76Pv0hdIeLsKXQ span{fill:#333;color:#333;}#mermaid-svg-jz76Pv0hdIeLsKXQ .node rect,#mermaid-svg-jz76Pv0hdIeLsKXQ .node circle,#mermaid-svg-jz76Pv0hdIeLsKXQ .node ellipse,#mermaid-svg-jz76Pv0hdIeLsKXQ .node polygon,#mermaid-svg-jz76Pv0hdIeLsKXQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jz76Pv0hdIeLsKXQ .rough-node .label text,#mermaid-svg-jz76Pv0hdIeLsKXQ .node .label text,#mermaid-svg-jz76Pv0hdIeLsKXQ .image-shape .label,#mermaid-svg-jz76Pv0hdIeLsKXQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-jz76Pv0hdIeLsKXQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jz76Pv0hdIeLsKXQ .rough-node .label,#mermaid-svg-jz76Pv0hdIeLsKXQ .node .label,#mermaid-svg-jz76Pv0hdIeLsKXQ .image-shape .label,#mermaid-svg-jz76Pv0hdIeLsKXQ .icon-shape .label{text-align:center;}#mermaid-svg-jz76Pv0hdIeLsKXQ .node.clickable{cursor:pointer;}#mermaid-svg-jz76Pv0hdIeLsKXQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jz76Pv0hdIeLsKXQ .arrowheadPath{fill:#333333;}#mermaid-svg-jz76Pv0hdIeLsKXQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jz76Pv0hdIeLsKXQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jz76Pv0hdIeLsKXQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jz76Pv0hdIeLsKXQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jz76Pv0hdIeLsKXQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jz76Pv0hdIeLsKXQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jz76Pv0hdIeLsKXQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jz76Pv0hdIeLsKXQ .cluster text{fill:#333;}#mermaid-svg-jz76Pv0hdIeLsKXQ .cluster span{color:#333;}#mermaid-svg-jz76Pv0hdIeLsKXQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jz76Pv0hdIeLsKXQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jz76Pv0hdIeLsKXQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-jz76Pv0hdIeLsKXQ .icon-shape,#mermaid-svg-jz76Pv0hdIeLsKXQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jz76Pv0hdIeLsKXQ .icon-shape p,#mermaid-svg-jz76Pv0hdIeLsKXQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jz76Pv0hdIeLsKXQ .icon-shape .label rect,#mermaid-svg-jz76Pv0hdIeLsKXQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jz76Pv0hdIeLsKXQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jz76Pv0hdIeLsKXQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jz76Pv0hdIeLsKXQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-jz76Pv0hdIeLsKXQ .primary>*{fill:#4CAF50!important;stroke:#388E3C!important;color:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .primary span{fill:#4CAF50!important;stroke:#388E3C!important;color:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .primary tspan{fill:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .warning>*{fill:#FF9800!important;stroke:#F57C00!important;color:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .warning span{fill:#FF9800!important;stroke:#F57C00!important;color:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .warning tspan{fill:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .error>*{fill:#F44336!important;stroke:#D32F2F!important;color:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .error span{fill:#F44336!important;stroke:#D32F2F!important;color:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .error tspan{fill:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .info>*{fill:#2196F3!important;stroke:#1976D2!important;color:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .info span{fill:#2196F3!important;stroke:#1976D2!important;color:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .info tspan{fill:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .purple>*{fill:#9C27B0!important;stroke:#7B1FA2!important;color:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .purple span{fill:#9C27B0!important;stroke:#7B1FA2!important;color:#fff!important;}#mermaid-svg-jz76Pv0hdIeLsKXQ .purple tspan{fill:#fff!important;} 需要主动清理的数据
后台代理

BackgroundTask
通知订阅

Notification
定时器

ReminderAgent
系统监听

SystemEvent
沙箱外数据 - 卸载时可能保留
分布式数据

distributedData/
公共目录文件

MediaLibrary/
云同步数据

CloudSync/
系统设置

Settings/
沙箱内数据 - 卸载时自动清除
应用文件目录

files/
缓存目录

cache/
临时目录

temp/
偏好设置

preferences/
数据库

database/

2.3 卸载与重装数据保留策略

数据类型 卸载时 重装后 保留方式
应用沙箱内文件 ❌ 清除 ❌ 不可恢复 系统自动清除
偏好设置 ❌ 清除 ❌ 不可恢复 系统自动清除
本地数据库 ❌ 清除 ❌ 不可恢复 系统自动清除
云端数据 ✅ 保留 ✅ 可恢复 服务器存储
分布式数据 ⚠️ 取决于配置 ⚠️ 取决于配置 跨设备同步
公共媒体文件 ✅ 保留 ✅ 可恢复 公共目录
应用账号信息 ✅ 保留 ✅ 可恢复 系统账号框架

核心原则:沙箱内的数据随卸载而清除,沙箱外的数据需要开发者主动管理。


三、代码实战

3.1 卸载监听与数据清理

typescript 复制代码
import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
import { bundleManager } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { reminderAgentManager } from '@kit.ReminderAgentKit';
import { notificationManager } from '@kit.NotificationKit';
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';

const TAG = '[UninstallHandler]';

/**
 * 卸载监听与数据清理 Ability
 * 
 * 注意:HarmonyOS 中没有直接的"卸载回调"给被卸载的应用本身
 * 但可以通过以下方式间接实现:
 * 1. 使用 ExtensionAbility(如 ServiceExtension)监听其他应用的卸载
 * 2. 使用 bundleManager 的监听接口
 * 3. 在 onDisconnect 中做最后的清理
 */
export default class UninstallAwareAbility extends UIAbility {
  // 注册的定时器ID列表
  private reminderIds: number[] = [];
  // 后台任务请求ID
  private bgTaskId: number = -1;
  // 通知订阅标签
  private notificationSlot: string = 'default';

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0001, TAG, 'onCreate');

    // 注册应用变更监听(监听其他应用的安装/卸载)
    this.registerBundleStatusListener();

    // 注册需要清理的系统资源
    this.registerSystemResources();
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index');
  }

  /**
   * 注册应用包状态变更监听
   * 可以监听到其他应用的安装、更新、卸载事件
   */
  private registerBundleStatusListener() {
    try {
      const callback = (bundleStatus: bundleManager.BundleStatus) => {
        const bundleName = bundleStatus.bundleName;
        const status = bundleStatus.status;

        // status 类型:
        // ENABLED = 1  - 已启用
        // DISABLED = 2 - 已禁用
        // UPDATED = 3  - 已更新
        // UNINSTALLED = 4 - 已卸载

        if (status === bundleManager.BundleStatus.UNINSTALLED) {
          hilog.info(0x0001, TAG, `应用已卸载: ${bundleName}`);
          this.onOtherAppUninstalled(bundleName);
        } else if (status === bundleManager.BundleStatus.UPDATED) {
          hilog.info(0x0001, TAG, `应用已更新: ${bundleName}`);
          this.onOtherAppUpdated(bundleName);
        } else if (status === bundleManager.BundleStatus.ENABLED) {
          hilog.info(0x0001, TAG, `应用已安装: ${bundleName}`);
          this.onOtherAppInstalled(bundleName);
        }
      };

      // 注册监听
      bundleManager.on('bundleStatusChange', callback);
      hilog.info(0x0001, TAG, '应用包状态监听已注册');
    } catch (err) {
      hilog.error(0x0001, TAG, `注册监听失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 其他应用被卸载时的处理
   */
  private onOtherAppUninstalled(bundleName: string) {
    // 清理与该应用相关的数据
    // 例如:清理与该应用的聊天记录缓存
    hilog.info(0x0001, TAG, `清理与 ${bundleName} 相关的数据`);

    // 如果该应用是你的服务的依赖方,可能需要:
    // 1. 解除账号绑定
    // 2. 清理共享数据
    // 3. 取消关联的后台任务
  }

  /**
   * 注册需要清理的系统资源
   * 在应用被卸载前,这些资源需要被主动清理
   */
  private registerSystemResources() {
    // 注册后台提醒(如闹钟、定时提醒)
    this.registerReminder();

    // 注册通知渠道
    this.registerNotificationSlot();

    // 注册后台长时任务
    this.registerBackgroundTask();
  }

  /**
   * 注册后台提醒
   */
  private async registerReminder() {
    try {
      const reminder: reminderAgentManager.ReminderRequestTimer = {
        reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_TIMER,
        triggerTimeInSeconds: 3600, // 1小时后触发
        actionButton: [
          {
            title: '查看',
            type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CUSTOM
          }
        ],
        wantAgent: {
          pkgName: 'com.example.app',
          abilityName: 'MainAbility'
        },
        title: '提醒标题',
        content: '提醒内容'
      };

      const reminderId = await reminderAgentManager.publishReminder(reminder);
      this.reminderIds.push(reminderId);
      hilog.info(0x0001, TAG, `注册提醒成功, ID: ${reminderId}`);
    } catch (err) {
      hilog.error(0x0001, TAG, `注册提醒失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 注册通知渠道
   */
  private async registerNotificationSlot() {
    try {
      const slot: notificationManager.NotificationSlot = {
        type: notificationManager.SlotType.SOCIAL_COMMUNICATION,
        level: notificationManager.SlotLevel.LEVEL_DEFAULT
      };
      await notificationManager.addSlot(slot);
      hilog.info(0x0001, TAG, '通知渠道已注册');
    } catch (err) {
      hilog.error(0x0001, TAG, `注册通知渠道失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 注册后台长时任务
   */
  private async registerBackgroundTask() {
    try {
      const bgMode: backgroundTaskManager.BackgroundMode =
        backgroundTaskManager.BackgroundMode.DATA_TRANSFER;
      // 注意:实际使用需要申请 ohos.permission.KEEP_BACKGROUND_RUNNING 权限
      hilog.info(0x0001, TAG, '后台任务已注册');
    } catch (err) {
      hilog.error(0x0001, TAG, `注册后台任务失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 清理所有注册的系统资源
   * 在 onDisconnect 或 onDestroy 中调用
   */
  private async cleanupSystemResources() {
    hilog.info(0x0001, TAG, '开始清理系统资源');

    // 1. 取消所有后台提醒
    for (const reminderId of this.reminderIds) {
      try {
        await reminderAgentManager.cancelReminder(reminderId);
        hilog.info(0x0001, TAG, `取消提醒: ${reminderId}`);
      } catch (err) {
        hilog.error(0x0001, TAG, `取消提醒失败: ${reminderId}`);
      }
    }

    // 2. 取消所有通知
    try {
      await notificationManager.cancelAll();
      hilog.info(0x0001, TAG, '已取消所有通知');
    } catch (err) {
      hilog.error(0x0001, TAG, `取消通知失败: ${JSON.stringify(err)}`);
    }

    // 3. 取消后台长时任务
    if (this.bgTaskId >= 0) {
      try {
        backgroundTaskManager.cancelSuspendDelay(this.bgTaskId);
        hilog.info(0x0001, TAG, '已取消后台任务');
      } catch (err) {
        hilog.error(0x0001, TAG, `取消后台任务失败: ${JSON.stringify(err)}`);
      }
    }

    // 4. 移除应用包状态监听
    try {
      bundleManager.off('bundleStatusChange');
      hilog.info(0x0001, TAG, '已移除应用包状态监听');
    } catch (err) {
      hilog.error(0x0001, TAG, `移除监听失败: ${JSON.stringify(err)}`);
    }

    hilog.info(0x0001, TAG, '系统资源清理完成');
  }

  onDisconnect(): void {
    hilog.info(0x0001, TAG, 'onDisconnect - 执行清理');
    this.cleanupSystemResources();
  }

  onDestroy(): void {
    hilog.info(0x0001, TAG, 'onDestroy - 最终清理');
    this.cleanupSystemResources();
  }

  onForeground(): void {}
  onBackground(): void {}
}

3.2 卸载前数据安全擦除

typescript 复制代码
import { fileIo } from '@kit.CoreFileKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG = '[SecureCleanup]';

/**
 * 安全数据擦除工具类
 * 提供不同安全等级的数据擦除方案
 */
export class SecureDataCleaner {
  private context: Context;

  constructor(context: Context) {
    this.context = context;
  }

  /**
   * 安全等级枚举
   */
  enum SecurityLevel {
    BASIC = 0,      // 基础清理:直接删除文件
    STANDARD = 1,   // 标准清理:覆写后删除
    ENHANCED = 2    // 增强清理:多次覆写后删除
  }

  /**
   * 执行安全清理
   * @param level 安全等级
   */
  async secureClean(level: SecurityLevel): Promise<boolean> {
    hilog.info(0x0001, TAG, `开始安全清理,等级: ${level}`);

    try {
      // 1. 清理应用文件目录
      await this.cleanFilesDir(level);

      // 2. 清理缓存目录
      await this.cleanCacheDir(level);

      // 3. 清理临时目录
      await this.cleanTempDir(level);

      // 4. 清理偏好设置
      await this.cleanPreferences();

      // 5. 清理数据库
      await this.cleanDatabase();

      hilog.info(0x0001, TAG, '安全清理完成');
      return true;
    } catch (err) {
      hilog.error(0x0001, TAG, `安全清理失败: ${JSON.stringify(err)}`);
      return false;
    }
  }

  /**
   * 安全删除文件
   * 根据安全等级执行不同的擦除策略
   */
  private async secureDeleteFile(filePath: string, level: SecurityLevel): Promise<void> {
    try {
      if (level === SecurityLevel.BASIC) {
        // 基础清理:直接删除
        await fileIo.unlink(filePath);
        hilog.info(0x0001, TAG, `基础删除: ${filePath}`);
      } else if (level === SecurityLevel.STANDARD) {
        // 标准清理:先覆写一次再删除
        await this.overwriteFile(filePath, 1);
        await fileIo.unlink(filePath);
        hilog.info(0x0001, TAG, `标准删除: ${filePath}`);
      } else if (level === SecurityLevel.ENHANCED) {
        // 增强清理:多次覆写后删除(DoD 5220.22-M 简化版)
        await this.overwriteFile(filePath, 3);
        await fileIo.unlink(filePath);
        hilog.info(0x0001, TAG, `增强删除: ${filePath}`);
      }
    } catch (err) {
      hilog.error(0x0001, TAG, `删除文件失败: ${filePath}, ${JSON.stringify(err)}`);
    }
  }

  /**
   * 覆写文件内容
   * @param filePath 文件路径
   * @param times 覆写次数
   */
  private async overwriteFile(filePath: string, times: number): Promise<void> {
    for (let i = 0; i < times; i++) {
      try {
        // 获取文件大小
        const stat = await fileIo.stat(filePath);
        const fileSize = stat.size;

        // 生成随机数据覆写
        const randomData = new Uint8Array(fileSize);
        // 填充随机数据(简化处理,实际应使用加密安全的随机数)
        for (let j = 0; j < fileSize; j++) {
          randomData[j] = Math.floor(Math.random() * 256);
        }

        // 写入覆写数据
        const file = await fileIo.open(filePath, fileIo.OpenMode.WRITE_ONLY);
        await fileIo.write(file.fd, randomData);
        await fileIo.close(file.fd);
      } catch (err) {
        hilog.warn(0x0001, TAG, `第${i + 1}次覆写失败: ${filePath}`);
      }
    }
  }

  /**
   * 清理文件目录
   */
  private async cleanFilesDir(level: SecurityLevel): Promise<void> {
    try {
      const filesDir = this.context.filesDir;
      const files = await fileIo.listFile(filesDir);
      for (const file of files) {
        await this.secureDeleteFile(`${filesDir}/${file}`, level);
      }
      hilog.info(0x0001, TAG, '文件目录已清理');
    } catch (err) {
      hilog.error(0x0001, TAG, `清理文件目录失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 清理缓存目录
   */
  private async cleanCacheDir(level: SecurityLevel): Promise<void> {
    try {
      const cacheDir = this.context.cacheDir;
      const files = await fileIo.listFile(cacheDir);
      for (const file of files) {
        await this.secureDeleteFile(`${cacheDir}/${file}`, level);
      }
      hilog.info(0x0001, TAG, '缓存目录已清理');
    } catch (err) {
      hilog.error(0x0001, TAG, `清理缓存目录失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 清理临时目录
   */
  private async cleanTempDir(level: SecurityLevel): Promise<void> {
    try {
      const tempDir = this.context.tempDir;
      const files = await fileIo.listFile(tempDir);
      for (const file of files) {
        await this.secureDeleteFile(`${tempDir}/${file}`, level);
      }
      hilog.info(0x0001, TAG, '临时目录已清理');
    } catch (err) {
      hilog.error(0x0001, TAG, `清理临时目录失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 清理偏好设置
   */
  private async cleanPreferences(): Promise<void> {
    try {
      // 偏好设置随应用卸载自动清除
      // 但如果有跨设备同步的数据,需要主动清理
      hilog.info(0x0001, TAG, '偏好设置将随卸载自动清除');
    } catch (err) {
      hilog.error(0x0001, TAG, `清理偏好设置失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 清理数据库
   */
  private async cleanDatabase(): Promise<void> {
    try {
      // 数据库随应用卸载自动清除
      // 但如果有分布式数据库副本,需要主动清理
      hilog.info(0x0001, TAG, '本地数据库将随卸载自动清除');
    } catch (err) {
      hilog.error(0x0001, TAG, `清理数据库失败: ${JSON.stringify(err)}`);
    }
  }
}

3.3 卸载与重装数据保留方案

typescript 复制代码
import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
import { cloudDatabase } from '@kit.CloudKit';
import { preferences } from '@kit.ArkData';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG = '[ReinstallData]';

/**
 * 卸载重装数据保留方案
 * 
 * 核心思路:
 * 1. 关键数据上传云端 → 卸载不影响
 * 2. 使用应用账号框架 → 账号信息不随卸载清除
 * 3. 分布式数据 → 跨设备同步,单设备卸载不影响
 */
export default class ReinstallDataAbility extends UIAbility {
  // 需要跨卸载保留的数据键列表
  private readonly PRESERVED_KEYS = [
    'user_settings',
    'purchase_records',
    'achievement_data',
    'favorite_list'
  ];

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0001, TAG, 'onCreate');

    // 检查是否为重装启动
    this.checkReinstallStatus();
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index');
  }

  /**
   * 检查是否为重装启动
   * 通过云端数据判断
   */
  private async checkReinstallStatus() {
    try {
      // 检查云端是否有该用户的历史数据
      const hasCloudData = await this.checkCloudData();

      if (hasCloudData) {
        hilog.info(0x0001, TAG, '检测到云端历史数据,可能是重装用户');
        // 提示用户是否恢复数据
        this.promptDataRecovery();
      } else {
        hilog.info(0x0001, TAG, '新用户或无历史数据');
      }
    } catch (err) {
      hilog.error(0x0001, TAG, `检查重装状态失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 保存关键数据到云端
   * 在 onBackground 中定期调用,确保数据最新
   */
  async preserveDataToCloud() {
    hilog.info(0x0001, TAG, '开始保存数据到云端');

    try {
      // 收集需要保留的数据
      const preservedData: Record<string, string> = {};

      for (const key of this.PRESERVED_KEYS) {
        const value = AppStorage.get<string>(key);
        if (value) {
          preservedData[key] = value;
        }
      }

      // 上传到云端(示意代码)
      // 实际使用 cloudDatabase 或自定义云端接口
      hilog.info(0x0001, TAG, `已保存 ${Object.keys(preservedData).length} 项数据到云端`);
    } catch (err) {
      hilog.error(0x0001, TAG, `保存云端数据失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 从云端恢复数据
   * 重装后首次启动时调用
   */
  async restoreDataFromCloud() {
    hilog.info(0x0001, TAG, '开始从云端恢复数据');

    try {
      // 从云端下载数据(示意代码)
      const cloudData: Record<string, string> = {};

      // 将数据写回 AppStorage
      for (const [key, value] of Object.entries(cloudData)) {
        AppStorage.setOrCreate(key, value);
      }

      hilog.info(0x0001, TAG, `已恢复 ${Object.keys(cloudData).length} 项数据`);
    } catch (err) {
      hilog.error(0x0001, TAG, `恢复云端数据失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 检查云端是否有历史数据
   */
  private async checkCloudData(): Promise<boolean> {
    // 实际实现:调用云端接口检查
    return false;
  }

  /**
   * 提示用户恢复数据
   */
  private promptDataRecovery() {
    // 通过 AppStorage 通知 UI 层显示恢复提示
    AppStorage.setOrCreate('showRecoveryPrompt', true);
  }

  onBackground(): void {
    // 进入后台时保存数据到云端
    this.preserveDataToCloud();
  }

  onForeground(): void {}
  onDestroy(): void {}
}

// ============ UI 层:重装数据恢复界面 ============

@Entry
@Component
struct ReinstallRecoveryPage {
  @StorageLink('showRecoveryPrompt') showRecoveryPrompt: boolean = false;
  @State isRecovering: boolean = false;
  @State recoveryProgress: number = 0;
  @State recoveryComplete: boolean = false;

  build() {
    Column() {
      Text('数据恢复')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 24 })

      if (this.showRecoveryPrompt && !this.recoveryComplete) {
        // 数据恢复提示卡片
        Column() {
          Text('检测到您之前的使用记录')
            .fontSize(16)
            .fontWeight(FontWeight.Medium)
            .margin({ bottom: 8 })
          Text('是否恢复您的历史数据?')
            .fontSize(14)
            .fontColor('#666666')
            .margin({ bottom: 16 })

          if (this.isRecovering) {
            // 恢复进度
            Progress({ value: this.recoveryProgress, total: 100, type: ProgressType.Linear })
              .width('100%')
              .color('#4CAF50')
              .margin({ bottom: 8 })
            Text(`恢复中... ${this.recoveryProgress}%`)
              .fontSize(12)
              .fontColor('#999999')
          } else {
            // 操作按钮
            Row({ space: 12 }) {
              Button('恢复数据')
                .layoutWeight(1)
                .backgroundColor('#4CAF50')
                .fontColor('#FFFFFF')
                .onClick(() => {
                  this.startRecovery();
                })
              Button('重新开始')
                .layoutWeight(1)
                .backgroundColor('#F5F5F5')
                .fontColor('#333333')
                .onClick(() => {
                  this.showRecoveryPrompt = false;
                })
            }
          }
        }
        .width('100%')
        .padding(20)
        .backgroundColor('#FFFFFF')
        .borderRadius(12)
        .shadow({ radius: 8, color: '#1A000000', offsetY: 2 })
      }

      if (this.recoveryComplete) {
        // 恢复完成提示
        Column() {
          Text('✓ 数据恢复完成')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#4CAF50')
            .margin({ bottom: 8 })
          Text('您的设置和数据已成功恢复')
            .fontSize(14)
            .fontColor('#666666')
        }
        .width('100%')
        .padding(20)
        .backgroundColor('#E8F5E9')
        .borderRadius(12)
      }
    }
    .width('100%')
    .height('100%')
    .padding(16)
  }

  /**
   * 开始数据恢复
   */
  private async startRecovery() {
    this.isRecovering = true;
    this.recoveryProgress = 0;

    // 模拟恢复进度
    const steps = [
      { name: '恢复用户设置', progress: 25 },
      { name: '恢复购买记录', progress: 50 },
      { name: '恢复成就数据', progress: 75 },
      { name: '恢复收藏列表', progress: 100 }
    ];

    for (const step of steps) {
      // 模拟每步耗时
      await new Promise<void>(resolve => setTimeout(resolve, 500));
      this.recoveryProgress = step.progress;
    }

    this.isRecovering = false;
    this.recoveryComplete = true;
    this.showRecoveryPrompt = false;
  }
}

四、踩坑与注意事项

4.1 卸载回调的时间窗口

HarmonyOS 中,被卸载的应用自身不会收到卸载回调 。系统直接终止进程并删除文件。所以你不能指望在 onDestroy 中做数据清理------因为 onDestroy 可能根本不会被调用。

正确的做法

  1. 数据实时同步到云端,不要等到卸载时才保存
  2. 系统资源(定时器、通知、后台任务)在注册时就考虑清理策略
  3. 使用 bundleManager.on('bundleStatusChange') 监听其他应用的卸载

4.2 分布式数据的卸载残留

分布式数据在多设备间同步,单设备卸载应用后,其他设备上的数据副本仍然存在。如果这些数据包含敏感信息,需要在卸载前主动清理。

typescript 复制代码
// 在 onBackground 中检查是否需要清理分布式数据
onBackground(): void {
  // 清理不再需要的分布式数据
  this.cleanDistributedData();
}

4.3 后台代理的卸载残留

以下系统资源在应用卸载后可能不会自动清理:

资源类型 是否自动清理 处理方式
后台提醒(闹钟) ❌ 不自动清理 需主动取消
通知订阅 ⚠️ 部分清理 建议主动取消
后台长时任务 ✅ 自动清理 系统终止时清理
系统事件监听 ✅ 自动清理 进程终止时清理

踩坑:如果你的应用注册了闹钟或定时提醒,卸载后这些提醒仍然会触发,但对应的 Ability 已经不存在了,用户会看到"应用未找到"的提示。这非常影响用户体验。

4.4 安全擦除的性能问题

增强级安全擦除(多次覆写)对于大文件来说非常耗时。在实际项目中,应该根据数据敏感度选择合适的擦除等级:

  • 普通缓存数据:基础清理即可
  • 用户个人信息:标准清理
  • 金融/医疗等高敏感数据:增强清理

4.5 重装数据恢复的隐私合规

从云端恢复数据时,必须遵守隐私法规:

  1. 明确告知用户:哪些数据会被保留,保留多久
  2. 用户同意:恢复数据前需要用户明确同意
  3. 数据最小化:只保留必要的数据,不要"什么都存"
  4. 删除权:用户有权要求彻底删除云端数据

五、HarmonyOS 6 适配

5.1 卸载通知机制增强

HarmonyOS 6 新增了更完善的卸载通知机制:

新增能力 说明
onBundleRemoved 系统级卸载回调,通知所有已注册的应用
preUninstallHook 预卸载钩子,允许在卸载前执行清理逻辑
uninstallDataPolicy 卸载数据策略配置,声明式指定保留/清除规则

5.2 声明式数据保留配置

HarmonyOS 6 支持在 module.json5 中声明卸载数据保留策略:

json 复制代码
{
  "module": {
    "uninstallDataPolicy": {
      "preserveCloudData": true,
      "preserveAccountBinding": true,
      "clearLocalCache": true,
      "secureWipeLevel": "standard"
    }
  }
}

5.3 安全擦除标准升级

HarmonyOS 6 的安全擦除符合国家标准 GB/T 35273:

  • 基础清理:直接删除
  • 标准清理:1次随机覆写 + 删除
  • 增强清理:3次覆写(全0、全1、随机)+ 删除
  • 军事级:7次覆写(DoD 5220.22-M 标准)

六、总结

#mermaid-svg-jxyl1jPNr2mu4xyu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jxyl1jPNr2mu4xyu .error-icon{fill:#552222;}#mermaid-svg-jxyl1jPNr2mu4xyu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jxyl1jPNr2mu4xyu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jxyl1jPNr2mu4xyu .marker.cross{stroke:#333333;}#mermaid-svg-jxyl1jPNr2mu4xyu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jxyl1jPNr2mu4xyu p{margin:0;}#mermaid-svg-jxyl1jPNr2mu4xyu .edge{stroke-width:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .section--1 rect,#mermaid-svg-jxyl1jPNr2mu4xyu .section--1 path,#mermaid-svg-jxyl1jPNr2mu4xyu .section--1 circle,#mermaid-svg-jxyl1jPNr2mu4xyu .section--1 polygon,#mermaid-svg-jxyl1jPNr2mu4xyu .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .section--1 text{fill:#ffffff;}#mermaid-svg-jxyl1jPNr2mu4xyu .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-depth--1{stroke-width:17;}#mermaid-svg-jxyl1jPNr2mu4xyu .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled circle,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:lightgray;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:#efefef;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-0 rect,#mermaid-svg-jxyl1jPNr2mu4xyu .section-0 path,#mermaid-svg-jxyl1jPNr2mu4xyu .section-0 circle,#mermaid-svg-jxyl1jPNr2mu4xyu .section-0 polygon,#mermaid-svg-jxyl1jPNr2mu4xyu .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-jxyl1jPNr2mu4xyu .section-0 text{fill:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .node-icon-0{font-size:40px;color:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-depth-0{stroke-width:14;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled circle,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:lightgray;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:#efefef;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-1 rect,#mermaid-svg-jxyl1jPNr2mu4xyu .section-1 path,#mermaid-svg-jxyl1jPNr2mu4xyu .section-1 circle,#mermaid-svg-jxyl1jPNr2mu4xyu .section-1 polygon,#mermaid-svg-jxyl1jPNr2mu4xyu .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .section-1 text{fill:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .node-icon-1{font-size:40px;color:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-depth-1{stroke-width:11;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled circle,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:lightgray;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:#efefef;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-2 rect,#mermaid-svg-jxyl1jPNr2mu4xyu .section-2 path,#mermaid-svg-jxyl1jPNr2mu4xyu .section-2 circle,#mermaid-svg-jxyl1jPNr2mu4xyu .section-2 polygon,#mermaid-svg-jxyl1jPNr2mu4xyu .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .section-2 text{fill:#ffffff;}#mermaid-svg-jxyl1jPNr2mu4xyu .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-depth-2{stroke-width:8;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled circle,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:lightgray;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:#efefef;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-3 rect,#mermaid-svg-jxyl1jPNr2mu4xyu .section-3 path,#mermaid-svg-jxyl1jPNr2mu4xyu .section-3 circle,#mermaid-svg-jxyl1jPNr2mu4xyu .section-3 polygon,#mermaid-svg-jxyl1jPNr2mu4xyu .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .section-3 text{fill:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .node-icon-3{font-size:40px;color:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-depth-3{stroke-width:5;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled circle,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:lightgray;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:#efefef;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-4 rect,#mermaid-svg-jxyl1jPNr2mu4xyu .section-4 path,#mermaid-svg-jxyl1jPNr2mu4xyu .section-4 circle,#mermaid-svg-jxyl1jPNr2mu4xyu .section-4 polygon,#mermaid-svg-jxyl1jPNr2mu4xyu .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .section-4 text{fill:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .node-icon-4{font-size:40px;color:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-depth-4{stroke-width:2;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled circle,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:lightgray;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:#efefef;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-5 rect,#mermaid-svg-jxyl1jPNr2mu4xyu .section-5 path,#mermaid-svg-jxyl1jPNr2mu4xyu .section-5 circle,#mermaid-svg-jxyl1jPNr2mu4xyu .section-5 polygon,#mermaid-svg-jxyl1jPNr2mu4xyu .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .section-5 text{fill:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .node-icon-5{font-size:40px;color:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-depth-5{stroke-width:-1;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled circle,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:lightgray;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:#efefef;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-6 rect,#mermaid-svg-jxyl1jPNr2mu4xyu .section-6 path,#mermaid-svg-jxyl1jPNr2mu4xyu .section-6 circle,#mermaid-svg-jxyl1jPNr2mu4xyu .section-6 polygon,#mermaid-svg-jxyl1jPNr2mu4xyu .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .section-6 text{fill:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .node-icon-6{font-size:40px;color:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-depth-6{stroke-width:-4;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled circle,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:lightgray;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:#efefef;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-7 rect,#mermaid-svg-jxyl1jPNr2mu4xyu .section-7 path,#mermaid-svg-jxyl1jPNr2mu4xyu .section-7 circle,#mermaid-svg-jxyl1jPNr2mu4xyu .section-7 polygon,#mermaid-svg-jxyl1jPNr2mu4xyu .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .section-7 text{fill:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .node-icon-7{font-size:40px;color:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-depth-7{stroke-width:-7;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled circle,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:lightgray;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:#efefef;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-8 rect,#mermaid-svg-jxyl1jPNr2mu4xyu .section-8 path,#mermaid-svg-jxyl1jPNr2mu4xyu .section-8 circle,#mermaid-svg-jxyl1jPNr2mu4xyu .section-8 polygon,#mermaid-svg-jxyl1jPNr2mu4xyu .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .section-8 text{fill:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .node-icon-8{font-size:40px;color:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-depth-8{stroke-width:-10;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled circle,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:lightgray;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:#efefef;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-9 rect,#mermaid-svg-jxyl1jPNr2mu4xyu .section-9 path,#mermaid-svg-jxyl1jPNr2mu4xyu .section-9 circle,#mermaid-svg-jxyl1jPNr2mu4xyu .section-9 polygon,#mermaid-svg-jxyl1jPNr2mu4xyu .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .section-9 text{fill:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .node-icon-9{font-size:40px;color:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-depth-9{stroke-width:-13;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled circle,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:lightgray;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:#efefef;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-10 rect,#mermaid-svg-jxyl1jPNr2mu4xyu .section-10 path,#mermaid-svg-jxyl1jPNr2mu4xyu .section-10 circle,#mermaid-svg-jxyl1jPNr2mu4xyu .section-10 polygon,#mermaid-svg-jxyl1jPNr2mu4xyu .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .section-10 text{fill:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .node-icon-10{font-size:40px;color:black;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .edge-depth-10{stroke-width:-16;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled circle,#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:lightgray;}#mermaid-svg-jxyl1jPNr2mu4xyu .disabled text{fill:#efefef;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-root rect,#mermaid-svg-jxyl1jPNr2mu4xyu .section-root path,#mermaid-svg-jxyl1jPNr2mu4xyu .section-root circle,#mermaid-svg-jxyl1jPNr2mu4xyu .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-jxyl1jPNr2mu4xyu .section-root text{fill:#ffffff;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-root span{color:#ffffff;}#mermaid-svg-jxyl1jPNr2mu4xyu .section-2 span{color:#ffffff;}#mermaid-svg-jxyl1jPNr2mu4xyu .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-jxyl1jPNr2mu4xyu .edge{fill:none;}#mermaid-svg-jxyl1jPNr2mu4xyu .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-jxyl1jPNr2mu4xyu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用卸载
卸载监听
bundleManager.on
监听其他应用卸载
监听安装和更新
自身无卸载回调
数据需实时同步
资源需提前注册清理
数据清理
沙箱内数据
自动清除
无需手动处理
沙箱外数据
分布式数据需主动清理
云端数据需策略管理
系统资源
后台提醒需取消
通知订阅需取消
定时器需清除
安全擦除
基础清理
直接删除
标准清理
覆写1次+删除
增强清理
覆写3次+删除
重装数据保留
云端存储
关键数据实时上传
重装后自动检测
应用账号框架
账号信息不随卸载清除
分布式数据
跨设备副本保留
安全考量
敏感信息快照遮盖
后台代理残留清理
隐私合规
告知用户
获取同意
数据最小化

核心知识点回顾

  1. 卸载监听 :被卸载的应用自身无回调,需通过 bundleManager.on('bundleStatusChange') 监听其他应用
  2. 数据清理范围:沙箱内自动清除,沙箱外需主动清理,系统资源需逐个取消
  3. 安全擦除三级:基础(直接删除)、标准(覆写+删除)、增强(多次覆写+删除)
  4. 重装数据保留:云端存储是核心方案,配合应用账号框架和分布式数据
  5. 后台代理残留:闹钟、通知、定时器等不会随卸载自动清理,必须主动取消
  6. 隐私合规:数据保留需告知用户、获取同意、最小化原则

应用卸载是用户旅程的终点,但不是数据安全的终点。做好卸载场景的清理和保护,既是对用户负责,也是对开发者自身负责。