【OpenHarmony/HarmonyOS】BackupExtensionAbility 入门:游戏数据备份、恢复与版本兼容

本地 Preferences 能保证“应用还在、数据文件还在”时继续读取,却不等于用户换机、重装或系统迁移后仍能恢复。HarmonyOS 提供备份扩展能力,让系统在适当时机调用应用完成备份和恢复。但注册了 BackupExtensionAbility 并输出一行日志,只代表入口存在,不代表用户档案、战绩和晶石已经真正迁移。本篇以项目当前空实现为起点,说明备份声明、生命周期、数据分类、版本迁移和隐私边界。☁️

一、当前项目已经接入了哪一层

模块配置声明了一个 backup 类型扩展:

"extensionAbilities": [
  {
    "name": "EntryBackupAbility",
    "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
    "type": "backup",
    "exported": false,
    "metadata": [
      {
        "name": "ohos.extension.backup",
        "resource": "$profile:backup_config"
      }
    ]
  }
]

backup_config.json 允许备份恢复:

{
  "allowToBackupRestore": true
}

扩展类继承系统基类:

export default class EntryBackupAbility
  extends BackupExtensionAbility {

  async onBackup(): Promise<void> {
    hilog.info(DOMAIN, 'testTag', 'onBackup ok');
    await Promise.resolve();
  }

  async onRestore(
    bundleVersion: BundleVersion
  ): Promise<void> {
    hilog.info(
      DOMAIN,
      'testTag',
      'onRestore ok %{public}s',
      JSON.stringify(bundleVersion)
    );
    await Promise.resolve();
  }
}
部分 当前状态
manifest 扩展声明 已存在
metadata 与 profile 已存在
系统回调入口 已存在
选择业务数据 未实现
生成备份快照 未实现
恢复文件/Preferences 未实现
版本迁移 未实现
完整性与错误处理 未实现

所以准确说法是“项目注册了备份扩展骨架”,不能写成“已经支持完整换机恢复”。

二、备份与普通页面生命周期不同

BackupExtensionAbility 不是用户点击后显示的页面,而是由系统备份框架按生命周期调用。它不应该依赖 Index 页面已构建,也不应通过页面 @State 获取数据。

sequenceDiagram
    participant System as 系统备份框架
    participant Ext as EntryBackupAbility
    participant Data as 应用持久化数据
    System->>Ext: 创建备份扩展实例
    System->>Ext: onBackup()
    Ext->>Data: 读取允许备份的稳定数据
    Ext-->>System: 完成或报告失败
    Note over System,Ext: 换机/重装/恢复阶段
    System->>Ext: onRestore(bundleVersion)
    Ext->>Data: 校验版本并恢复
    Ext-->>System: 完成或报告失败

回调可能发生在主页面未打开、Manager 单例未初始化的环境中。因此备份代码应该使用扩展上下文可访问的持久化位置,并显式初始化所需数据访问,而不是假设 UserManager.getInstance().getCurrentUser() 已经可用。

三、先列数据清单,再决定备份策略

游戏项目当前有多组本地数据:

数据 价值 是否建议备份 原因
用户名称、预设头像 ID 恢复用户资料体验
自定义头像 URI 条件性 谨慎 原 URI 在另一设备可能无效
累计胜负、最高分 长期进度
晶石和升级等级 经济资产,需完整性与幂等
本地排行榜 可选 可恢复历史成绩
音量、振动、灵敏度、语言 可选 提升换机连续性
当前局坦克/子弹状态 瞬时且恢复复杂
附近设备、Peer IP 环境相关且可能敏感
Canvas 粒子、缓存 可重新生成
签名、密钥、Token 禁止 不应进入普通备份快照

备份不是把整个沙箱无脑复制。最小必要原则能减少体积、隐私风险和版本兼容负担。

四、Preferences 数据需要一致快照

项目将用户、升级和分数放在不同 Preferences 文件/键中。如果备份过程中一边读用户、一边正好发生结算加币,快照可能来自不同时间点。

一种演进方式是先构造统一快照对象:

interface BackupSnapshotV1 {
  schemaVersion: 1;
  createdAt: number;
  appVersion: string;
  userProfileJson: string;
  upgradeStateJson: string;
  scoreRecordsJson: string;
  settingsJson: string;
}

然后执行:

  1. 暂停或串行化资产写入;
  2. 读取各 Preferences 当前值;
  3. 校验 JSON 可以解析、关键字段合法;
  4. 写入临时快照;
  5. 完成后原子替换为正式备份文件;
  6. 释放锁,恢复业务写入。

以上是架构流程,不代表当前代码已实现,也没有绑定某一具体备份 API。实际文件交付方式应按目标 HarmonyOS API 文档实现。

五、为什么快照必须带 schemaVersion

bundleVersion 能告诉恢复端“备份来自哪个应用版本”,但业务数据结构可能独立变化。例如:

  • V1 只有 coins
  • V2 增加 speedLevelfireRateLevelshieldLevel
  • V3 将 avatar 从任意字符串改为来源对象;
  • V4 给排行榜增加模式和头像。

只依赖应用版本会把迁移逻辑和发版号紧密耦合。快照内部的 schemaVersion 更明确:

type BackupSnapshot =
  BackupSnapshotV1 |
  BackupSnapshotV2;

function migrateSnapshot(raw: Object): BackupSnapshotV2 {
  const version = readSchemaVersion(raw);
  if (version === 1) return migrateV1ToV2(raw);
  if (version === 2) return validateV2(raw);
  throw new Error('Unsupported backup schema');
}

迁移应逐版本进行,避免一个函数同时猜测多个历史结构。

六、BundleVersion 应如何参与决策

当前 onRestore(bundleVersion) 只把版本对象写入日志。真正恢复时可以用它做辅助判断:

情况 策略
来源版本等于当前版本 正常校验并恢复
来源版本较旧 执行向前迁移
来源版本较新 谨慎拒绝或仅恢复兼容字段
版本信息缺失 依赖 schemaVersion,进入保守模式

“新版本数据恢复到旧应用”尤其危险。旧代码不知道新字段语义,不能简单覆盖本地文件。可以保留原快照并提示需要升级应用,或只恢复明确向后兼容的用户设置。

日志中也不应该输出完整业务快照。当前只输出版本信息,且使用 %{public}s,没有包含用户名、手机号或资产数据。若版本对象未来含敏感扩展字段,应重新评估公开级别。

七、恢复不能直接覆盖:先校验再提交 🛡️

安全恢复流程可以分四阶段:

flowchart TD
    A[读取备份快照] --> B{格式与大小合法?}
    B --  --> X[拒绝并保留原数据]
    B --  --> C[校验 schema 与字段类型]
    C --> D{需要迁移?}
    D --  --> E[逐版本迁移]
    D --  --> F[使用当前结构]
    E --> F
    F --> G[写入临时 Preferences/文件]
    G --> H{读回验证成功?}
    H --  --> X
    H --  --> I[替换正式数据]

字段校验至少包含:

  • 晶石和等级是有限非负整数;
  • 等级不超过当前 MAX_LEVEL
  • 用户名长度和字符合法;
  • 头像是已知资源 ID 或允许的本地来源;
  • 排行榜条数不超过 Top 100 规则;
  • JSON 字符串和总文件大小有上限;
  • 时间戳在合理范围内。

即使备份来自系统框架,数据也可能因旧版本 bug、传输损坏或手工调试而异常。

八、恢复覆盖还是合并

设备上可能已有新数据,又收到旧备份。不同数据需要不同策略:

数据 推荐冲突策略 说明
用户 ID 保持已认证云 UID;本地游客需提示 身份不能随意合并
用户昵称/头像 取最新修改时间或让用户选择 属于偏好
晶石 不能简单相加 会重复创造资产
升级等级 可取较高值,但要与经济规则一致 仍需防作弊
最高分 取 max 天然可合并
累计局数/总分 需要事件去重 直接相加可能重复
设置 通常备份覆盖 用户可再次修改

离线单机可以选择“整份覆盖”,实现简单且可解释;云账号则应由服务器成为资产权威,设备备份只恢复非权威设置。不要同时把系统备份和云同步都当主数据源。

九、自定义头像为什么不能只备份 URI

PhotoViewPicker 返回的 datashare:// 或其他媒体 URI 可能依赖原设备授权和媒体库。把字符串搬到新设备,目标 URI 指向的内容并不存在。

可选策略:

  1. 预设头像只备份稳定资源 ID;
  2. 自定义头像先复制/压缩到应用私有文件,再将文件纳入允许备份范围;
  3. 云账号头像上传到受控对象存储,备份只保存头像版本 ID;
  4. 无法恢复时回退默认头像,不让整个用户档案恢复失败。

头像图片可能包含个人信息,是否随系统备份迁移应在隐私说明中明确。

十、错误处理不能只写“onBackup ok” ⚠️

当前日志在没有任何业务操作时输出 onBackup ok,会让排查者误以为备份成功。更准确的日志应包含阶段和非敏感计数:

backup_start schema=2
backup_snapshot_ready sections=4 records=37
backup_complete bytes=18420

失败时记录错误类别:读取失败、序列化失败、空间不足、校验失败、版本不支持。不要输出完整 JSON、手机号、用户 URI或密钥。

回调中的异常也不应被全部吞掉。系统需要知道任务失败,才能按框架能力重试或向用户报告。具体错误返回方式应遵循 BackupExtensionAbility 当前 API 契约。

十一、与 Manager 的依赖边界

EntryAbility 启动窗口时初始化 UserManager、UpgradeManager、ScoreManager;备份扩展可能不经过同一 UIAbility 启动路径。直接使用这些单例有两种风险:

  • pref 尚为 null,读取到默认空状态;
  • 单例保留旧 Context 或内存状态,与磁盘不一致。

可以让各 Manager 提供显式、幂等的 init(context),备份扩展等待初始化完成;更清晰的是建立纯数据仓库,只依赖 application context,UI Manager 和备份扩展共同调用。

备份任务应始终读取已经 flush 的持久化事实,而不是页面缓存的 @State totalCoins

十二、备份期间的并发和幂等

onBackup() 可能因重试被调用多次,onRestore() 也可能在失败后重来。操作必须幂等:

  • 相同源数据重复生成快照结果一致;
  • 恢复同一个 snapshotId 两次不会把晶石加两遍;
  • 临时文件使用唯一名称并在失败后清理;
  • 正式替换前保留旧数据备份;
  • 写入完成标志最后落盘;
  • 中途进程终止后能识别半成品。

经济数据尤其不能用“读取备份 coins,然后调用 addCoins”,应该恢复确定余额或执行带交易 ID 的合并。

十三、测试如何覆盖系统扩展 🧪

测试场景 关键断言
空白新用户备份 生成合法最小快照
完整用户/升级/排行榜 所有允许字段恢复一致
V1 快照恢复到 V2 默认字段补齐,旧值保留
新版本快照到旧应用 保守拒绝,不破坏本地数据
JSON 损坏 原数据保持不变
晶石为负数/NaN 校验失败
恢复中途写入失败 回滚到原数据
同一快照恢复两次 资产不重复增加
自定义头像 URI 失效 回退默认头像
备份时同时发生结算 快照属于一致时间点
日志扫描 无用户名、电话、完整快照

除了直接调用迁移纯函数,还应在真机或官方测试环境触发系统备份/恢复流程。只运行 onBackup() 单元测试不能证明 manifest、metadata 和系统调度都正确。

十四、实施顺序建议

可以按风险从低到高落地:

  1. 先恢复音量、语言等非关键设置;
  2. 加入用户基础资料和预设头像 ID;
  3. 加入排行榜并限制条数;
  4. 建立版本迁移和临时写入;
  5. 最后处理晶石与升级资产,加入幂等和完整性;
  6. 真机验证后再在产品文案中声明支持。

不要第一版就把所有沙箱文件加入备份。越是关键的资产,越需要晚一点、验证更充分地接入。

十五、总结 ✨

项目已经正确注册 BackupExtensionAbility、metadata 和允许备份的 profile,也具备 onBackup()onRestore(bundleVersion) 两个系统入口。但当前回调只记录日志,没有读取、写入或迁移任何业务数据,所以仍是骨架而非完整功能。

真正的备份恢复需要回答六个问题:备份哪些数据、如何获得一致快照、怎样标记 schema、旧版本如何迁移、恢复冲突怎样处理、失败如何回滚。对游戏而言还要特别保护晶石和升级资产,避免重复入账;自定义头像则不能只搬 URI。先把非关键设置做通,再逐步纳入资料、战绩和经济数据,才能把“onBackup ok”变成用户真正可依赖的换机体验。🚀


推荐标签: OpenHarmony HarmonyOS ArkTS BackupExtensionAbility 数据备份 版本迁移 Preferences 隐私安全

img

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐