应用备份恢复与数据安全
一、引言
用户换机、重装系统后,最痛的不是重新下载应用,而是"看过一半的收藏和设置全没了"。HarmonyOS 为应用提供了系统级备份恢复能力:通过声明BackupExtensionAbility与backup_config.json,应用的数据目录可在云备份、换机迁移等场景下被系统安全地打包、传输与还原。multi-short-video 的四个产品模块都实现了备份能力,本文以真实代码为样本,拆解备份扩展能力的声明、实现与数据安全设计,并给出"哪些数据该备份、哪些绝不能备份"的边界判定方法。
二、备份机制与配置声明
HarmonyOS 备份恢复由系统服务驱动:系统在备份时机(换机、云备份、恢复出厂等)唤醒应用注册的备份扩展能力,应用回调onBackup将数据交给系统归档;恢复时回调onRestore将数据重新写入。应用侧只需做两件事:声明扩展能力、提供备份配置。系统负责数据打包、加密传输、跨设备搬运这些重活,应用本身不需要感知备份的完整链路。
先看配置声明。本工程每个产品模块都在module.json5中注册了一个 type 为 backup 的 ExtensionAbility,并关联备份配置文件:
// products/default/src/main/module.json5(pc/tv/wearable 模块结构相同) "extensionAbilities": [ { "name": "MultiShortVideoDefaultBackupAbility", "srcEntry": "./ets/defaultbackupability/MultiShortVideoDefaultBackupAbility.ets", "type": "backup", "exported": false, "metadata": [ { "name": "ohos.extension.backup", "resource": "$profile:backup_config" } ] } ]关键点:type必须是backup;exported为 false,因为备份扩展只被系统服务调用,不应暴露给其他应用拉起;metadata中name固定为ohos.extension.backup,resource指向resources/base/profile/backup_config.json。该配置文件内容极简:
// products/default/src/main/resources/base/profile/backup_config.json { "allowToBackupRestore": true }allowToBackupRestore是总开关,置 true 表示应用允许被系统备份与恢复。该文件还支持更细粒度的控制字段:fullBackupOnly限定仅参与整机备份;excludes排除指定目录;includes指定参与备份的目录。字段优先级与继承规则在不同版本略有差异,官方文档是最终依据,实践时建议"先全量、后收紧":第一版只开总开关跑通链路,再逐步用 excludes 把缓存与日志排除掉。
三、备份扩展能力的实现
四个产品模块的备份 Ability 实现完全一致,以 default 模块为例:
// products/default/src/main/ets/defaultbackupability/MultiShortVideoDefaultBackupAbility.ets import { hilog } from '@kit.PerformanceAnalysisKit'; import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; const DOMAIN = 0x0000; export default class DefaultBackupAbility extends BackupExtensionAbility { async onBackup() { hilog.info(DOMAIN, 'testTag', 'onBackup ok'); await Promise.resolve(); } async onRestore(bundleVersion: BundleVersion) { hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); await Promise.resolve(); } }类继承自@kit.CoreFileKit的BackupExtensionAbility,实现两个钩子:
onBackup():系统准备备份应用数据时回调。若应用有需要预处理的逻辑(如把内存态状态刷入 Preferences、关闭正在写的文件、停止后台任务),在此执行;无特殊逻辑时保持空实现即可,系统会自动归档沙箱内数据。onRestore(bundleVersion):恢复完成后回调,参数携带备份来源的版本信息。可据此做数据迁移:例如备份来自 1.0.0,当前是 1.1.0,则在此处执行一次版本化迁移,保证老数据结构能平滑升级。日志中%{public}s为隐私占位符,%{public}s会脱敏打印、%{s}明文打印,这里使用%{public}s避免把 bundle 内部细节泄露到系统日志。
MultiShortVideoPcBackupAbility.ets、MultiShortVideoTvBackupAbility.ets、MultiShortVideoWearableBackupAbility.ets)除类名外完全同构,位于各自产品的pcbackupability、tvbackupability、wearablebackupability目录下,体现了"四个入口、同一备份策略"的设计——备份逻辑与 UI 无关,四端复用同一套能力实现,只是各挂一个入口类。使用备份扩展还要注意三个生命周期约束。其一,备份扩展是后台扩展,没有 UI 上下文,不能在 onBackup/onRestore 中弹窗或跳页面,任何需要用户确认的动作都应提前在前台完成。其二,回调有超时约束,系统不会无限等待,onBackup/onRestore 中的耗时操作(如网络上传、大规模文件整理)应在回调外异步完成,回调内只做必要的落盘与状态收尾,本工程的空实现加await Promise.resolve()正是这种"轻回调"的示范。其三,备份期间应用可能被系统冻结,不要依赖备份回调内的定时器或监听器,回调应当是"一次性、幂等"的——即使被调用两次,也不能产生重复数据或脏数据。
四、备份范围与数据安全设计
备份并非"全盘拷贝",系统只会归档应用沙箱内允许访问的目录(data/app/el2/100/base/包名 下的 files、preferences 等),数据库、缓存目录等默认可被备份。开发者应主动划定备份边界:
| 数据 | 是否应备份 | 说明 |
| 用户设置(Preferences 首选项) | 是 | 播放位置、页签索引、主题偏好 |
| 收藏/点赞本地缓存 | 是 | 换机后用户体验连续 |
| 视频缓存文件 | 否 | 体积大、可从网络重建 |
| 账号 Token/密钥 | 否 | 高敏数据,应走账号同步而非本地备份 |
| 日志、临时文件 | 否 | 无价值且增大备份体积 |
对不应备份的目录,可在backup_config.json中用excludes排除。数据安全上要遵循三条红线:
- 权限最小化:本工程仅声明一个受限权限
ohos.permission.DETECT_GESTURE(见 default 模块 module.json5 的 requestPermissions),备份链路本身不需要任何额外权限——这也是判断"备份配置是否合规"的简单标准:备份不应该成为申请权限的理由。 - 敏感数据不落盘:账号凭据应存于系统账密管理能力(
@kit.BasicServicesKit的 Asset Store),而不是 Preferences——后者会被备份机制打包,扩大泄露面。Asset Store 中的数据独立加密存储,系统备份时也不会随普通数据目录搬运。 - 日志脱敏:onRestore 打印版本信息使用
%{public}s而非%{s},避免把 bundle 内部数据带入系统日志;业务侧打印用户相关数据时也应统一脱敏。
备份包的加密由系统负责,开发者不需要也不应该自行加密整个数据目录——过度自研加密反而会破坏备份的可恢复性。正确的做法是"分级安全":普通偏好数据裸存、可备份;敏感凭据进 Asset Store、不备份。
五、卸载重装与账号数据隔离
备份恢复与"卸载重装"是两条不同的数据通路:
- 备份恢复:换机/云备份场景,系统级搬运整个数据目录,应用无感,用户无需登录即可找回本地数据。
- 卸载重装:默认清空沙箱数据;本工程
module.json5中deliveryWithInstall: false表示应用随设备预置或单独安装,installationFree: false表示不支持免安装运行——免安装场景下沙箱数据不保留,必须依赖备份或账号云端同步。
WorksDataModel、CommentDataModel这类模型与账号服务绑定,而不是塞进本地备份包。原因有二:一是账号资产跨端共享天然应由云端承载,本地备份无法解决"另一台设备"的同步;二是备份包一旦包含用户内容,就升级为个人信息处理场景,隐私合规成本陡增。六、备份恢复的验证与排障
备份能力接入后必须实测,不能只看配置。验证路径分两步:
- 安装后验证注册:安装应用后查看系统日志或使用
hdc shell bm dump --extension-type backup确认备份扩展已被系统识别,若看不到对应 Ability,说明 module.json5 的 metadata 声明或 backup_config.json 路径有误。 - 触发真实备份/恢复:通过系统"云备份"或换机助手触发一次备份,卸载重装(或在新设备安装)后执行恢复,验证
onBackup与onRestore日志按预期打印、Preferences 中的播放位置与页签索引确实还原。恢复后首次启动建议在onRestore中打印bundleVersion,人工核对来源版本与迁移分支是否命中。
排障时最常遇到的三类问题:
| 现象 | 可能原因 | 处置 |
| 备份扩展未被调用 | metadata 的 resource 指向错误 | 检查$profile:backup_config与文件是否同名 |
| 恢复后数据缺失 | 数据位于 excludes 排除目录 | 核对 backup_config 的排除规则 |
| 恢复后崩溃 | 备份版本数据结构与当前版本不兼容 | 在 onRestore 中按 BundleVersion 做迁移 |
这三个问题的共同点是把"备份"当成一次性配置,缺少版本视角:数据结构一旦演进,就必须同步维护恢复侧的迁移逻辑,否则备份反而成为升级的隐患。
七、总结与最佳实践
备份恢复是把"换机零丢失"变成产品卖点的系统能力,成本极低但收益明显。本工程四个产品模块通过"一个 backup 扩展 + 一个配置开关"即完成了能力接入,值得所有应用参考。最佳实践归纳为:
- 显式声明:在 module.json5 注册
type: backup扩展并在 metadata 中关联$profile:backup_config,allowToBackupRestore置 true。 - 默认空实现:无预处理逻辑时 onBackup/onRestore 保持空实现,系统自动完成归档;不要在里面做耗时业务,备份回调有超时约束。
- 版本化迁移:利用 onRestore 的
BundleVersion参数做数据结构迁移,兼容老版本备份包;升级数据模型时同步维护迁移逻辑。 - 划清备份边界:用 excludes 排除缓存与日志;Token 等敏感数据走 Asset/账号体系,绝不进入备份包。
- 卸载策略区分:明确"备份恢复"与"卸载重装"两条通路,账号数据与本地数据严格隔离,避免把云端资产误入本地备份引发隐私风险。
备份能力接入很简单,但"备份什么、不备份什么"才是真正的设计题。把数据分级模型想清楚,备份恢复就能成为多设备体验的加分项,而不是隐私合规的隐患。