【OpenHarmony/HarmonyOS】BackupExtensionAbility 入门:游戏数据备份、恢复与版本兼容
【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;
}
然后执行:
- 暂停或串行化资产写入;
- 读取各 Preferences 当前值;
- 校验 JSON 可以解析、关键字段合法;
- 写入临时快照;
- 完成后原子替换为正式备份文件;
- 释放锁,恢复业务写入。
以上是架构流程,不代表当前代码已实现,也没有绑定某一具体备份 API。实际文件交付方式应按目标 HarmonyOS API 文档实现。
五、为什么快照必须带 schemaVersion
bundleVersion 能告诉恢复端“备份来自哪个应用版本”,但业务数据结构可能独立变化。例如:
- V1 只有
coins; - V2 增加
speedLevel、fireRateLevel、shieldLevel; - 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 指向的内容并不存在。
可选策略:
- 预设头像只备份稳定资源 ID;
- 自定义头像先复制/压缩到应用私有文件,再将文件纳入允许备份范围;
- 云账号头像上传到受控对象存储,备份只保存头像版本 ID;
- 无法恢复时回退默认头像,不让整个用户档案恢复失败。
头像图片可能包含个人信息,是否随系统备份迁移应在隐私说明中明确。
十、错误处理不能只写“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 和系统调度都正确。
十四、实施顺序建议
可以按风险从低到高落地:
- 先恢复音量、语言等非关键设置;
- 加入用户基础资料和预设头像 ID;
- 加入排行榜并限制条数;
- 建立版本迁移和临时写入;
- 最后处理晶石与升级资产,加入幂等和完整性;
- 真机验证后再在产品文案中声明支持。
不要第一版就把所有沙箱文件加入备份。越是关键的资产,越需要晚一点、验证更充分地接入。
十五、总结 ✨
项目已经正确注册 BackupExtensionAbility、metadata 和允许备份的 profile,也具备 onBackup()、onRestore(bundleVersion) 两个系统入口。但当前回调只记录日志,没有读取、写入或迁移任何业务数据,所以仍是骨架而非完整功能。
真正的备份恢复需要回答六个问题:备份哪些数据、如何获得一致快照、怎样标记 schema、旧版本如何迁移、恢复冲突怎样处理、失败如何回滚。对游戏而言还要特别保护晶石和升级资产,避免重复入账;自定义头像则不能只搬 URI。先把非关键设置做通,再逐步纳入资料、战绩和经济数据,才能把“onBackup ok”变成用户真正可依赖的换机体验。🚀
推荐标签: OpenHarmony HarmonyOS ArkTS BackupExtensionAbility 数据备份 版本迁移 Preferences 隐私安全

更多推荐


所有评论(0)