前言
在移动应用中,数据备份与恢复是保障用户数据安全的核心能力。HarmonyOS 提供了BackupExtensionAbility这一标准化的数据备份框架,开发者只需要继承该类并实现onBackup和onRestore两个回调方法,系统即可自动调度备份任务,无需手动处理文件拷贝、压缩和传输等底层操作。本文以 小事记(xiaoshiji_ohos_app) 项目中的EntryBackupAbility.ets为切入点,深入解析BackupExtensionAbility的注册机制、生命周期回调、备份配置文件和版本管理策略。
核心特点:
- 简单易用:API 设计直观,上手成本低
- 性能优异:底层优化充分,运行效率高
- 扩展性强:支持自定义配置和扩展
本文参考 HarmonyOS 官方文档:application-models.md 和 application-package-structure-stage.md。
一、ExtensionAbility 体系概览
1.1 ExtensionAbility 的设计理念
ExtensionAbility是 HarmonyOS Stage 模型中用于后台任务的基类体系。与UIAbility不同,ExtensionAbility 没有 UI 界面,专注于在后台执行特定类型任务:
图:ExtensionAbility 的各类扩展及其适用场景
| 扩展类型 | 系统类 | 主要用途 |
|---|---|---|
| BackupExtensionAbility | @kit.CoreFileKit | 数据备份与恢复 |
| ServiceExtensionAbility | @kit.AbilityKit | 后台常驻服务 |
| FormExtensionAbility | @kit.FormKit | 桌面卡片(Widget) |
| WorkSchedulerExtensionAbility | @kit.BackgroundTasksKit | 延迟任务调度 |
| InputMethodExtensionAbility | @kit.InputMethodKit | 输入法应用 |
| AccessibilityExtensionAbility | @kit.AccessibilityKit | 无障碍服务 |
1.2 备份扩展的独特性
在众多 ExtensionAbility 类型中,BackupExtensionAbility有几个独特之处:
- 系统自动调度— 备份任务由系统而非用户手动触发,在设备充电、连接 Wi-Fi 且空闲时自动执行
- 增量备份机制— 系统只备份发生变化的数据,而非每次都全量备份
- 配置驱动— 通过
backup_config.json配置文件指定备份范围,无需代码干预 - 版本感知— 恢复时携带
BundleVersion参数,支持版本兼容性处理
// EntryBackupAbility.ets — 小事记的备份扩展实现 import { hilog } from '@kit.PerformanceAnalysisKit'; import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; const DOMAIN = 0x0000; export default class EntryBackupAbility 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(); } }二、备份扩展的注册与配置
2.1 在 module.json5 中注册
备份扩展需要在module.json5的extensionAbilities数组中注册:
{ "module": { "extensionAbilities": [ { "name": "EntryBackupAbility", "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", "type": "backup", "exported": false, "metadata": [ { "name": "ohos.extension.backup", "resource": "$profile:backup_config" } ] } ] } }各字段详解:
| 字段 | 值 | 说明 |
|---|---|---|
name | EntryBackupAbility | 扩展名称,模块内唯一 |
srcEntry | ./ets/entrybackupability/EntryBackupAbility.ets | 实现文件的路径 |
type | backup | 扩展类型,必须为backup |
exported | false | 不对外暴露,仅系统可调用 |
metadata | — | 包含系统约定的备份配置引用 |
提示:
type字段的值必须与系统定义的类型严格一致,"backup"不可拼写为"backupdata"或"databackup"。
2.2 备份配置文件的定义
metadata中的resource: "$profile:backup_config"引用了resources/base/profile/backup_config.json文件,该文件定义了备份的具体范围:
// resources/base/profile/backup_config.json { "allowToBackup": true, "includes": [ "data/storage/el2/database/", "data/storage/el2/base/preferences/" ], "excludes": [ "data/storage/el2/base/cache/", "data/storage/el2/base/temp/" ] }配置字段说明:
| 字段 | 类型 | 说明 | 是否必须 |
|---|---|---|---|
allowToBackup | boolean | 是否允许备份 | ✅ |
includes | string[] | 需要备份的路径列表 | ✅ |
excludes | string[] | 排除的路径列表 | ❌ |
fullBackupOnly | boolean | 是否仅全量备份 | ❌ |
路径规则:
- 路径相对于
data/storage/el2/base/(应用的文件根目录) - 支持目录路径(以
/结尾)和文件路径 - 不支持通配符(
*),但目录路径会递归包含所有子文件
2.3 文件分区模式
备份路径中的el2指的是加密分区模式,HarmonyOS 提供了两种文件分区:
| 分区模式 | 常量 | 说明 | 存储内容 |
|---|---|---|---|
| EL1 | AreaMode.EL1 | 设备级加密,开机即可访问 | 应用配置、缓存 |
| EL2 | AreaMode.EL2 | 用户级加密,需要解锁后访问 | 用户数据库、偏好设置 |
// 获取不同分区的路径 import { common } from '@kit.AbilityKit'; let context = this.context.getApplicationContext(); let el1Path = context.getDatabaseDir(); // EL1 分区数据库路径 let el2Path = context.getPreferencesDir(); // EL2 分区偏好设置路径小事记的备份配置包含了el2分区的数据库和偏好设置,因为用户的事件数据(LifeEvent)和设置项都存储在这个分区中。
三、onBackup 生命周期详解
3.1 备份触发时机
系统在以下场景会触发onBackup回调:
- 设备充电状态— 接入电源后
- 网络条件— 连接 Wi-Fi(非蜂窝网络)
- 空闲状态— 设备处于空闲状态
- 时间间隔— 距离上次备份超过 24 小时
以上条件全部满足时,系统才会触发备份。开发者无法手动触发备份,但可以通过onBackup回调中的代码执行自定义的预处理逻辑。
3.2 onBackup 的完整实现
当前小事记的onBackup只记录了日志,但在生产环境中,应该进行数据完整性校验:
// 增强版 onBackup 实现 import { hilog } from '@kit.PerformanceAnalysisKit'; import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; import { fileIo } from '@kit.CoreFileKit'; const DOMAIN = 0x0000; export default class EntryBackupAbility extends BackupExtensionAbility { async onBackup() { hilog.info(DOMAIN, 'testTag', 'onBackup started'); try { // 1. 检查数据库完整性 await this.checkDatabaseIntegrity(); // 2. 清理过期缓存,减少备份体积 await this.cleanExpiredCache(); // 3. 记录备份时间戳 await this.recordBackupTimestamp(); hilog.info(DOMAIN, 'testTag', 'onBackup completed'); } catch (err) { hilog.error(DOMAIN, 'testTag', 'onBackup failed: %{public}s', JSON.stringify(err)); throw err; // 抛出异常,系统会记录备份失败 } } private async checkDatabaseIntegrity(): Promise<void> { // 数据库完整性检查逻辑 // 如果数据损坏,在此处抛出自定义异常 } private async cleanExpiredCache(): Promise<void> { let cacheDir = this.context.cacheDir; // 清理 7 天前的缓存文件 // 减少备份体积 } private async recordBackupTimestamp(): Promise<void> { let lastBackupTime = new Date().toISOString(); // 将备份时间写入偏好设置 // 用于在 UI 中展示"上次备份时间" } }3.3 备份文件的解密与恢复
系统在备份时会对数据进行加密。备份数据存储在云端,用户无法直接查看备份文件内容,只能通过onRestore恢复。
四、onRestore 生命周期详解
4.1 恢复触发场景
onRestore在以下场景被触发:
- 用户在新设备登录— 首次启动应用时,系统检测到云端有备份数据
- 应用重装后— 卸载重装后,系统自动恢复备份数据
- 跨设备迁移— 通过华为账号将数据从旧设备迁移到新设备
4.2 BundleVersion 版本管理
onRestore的参数BundleVersion包含了备份数据的版本信息,用于处理版本兼容性:
// BundleVersion 的数据结构 interface BundleVersion { major: number; // 主版本号 minor: number; // 次版本号 patch: number; // 补丁版本号 build: number; // 构建号 versionName: string; // 版本名称,如 "1.0.0" }4.3 版本兼容性处理
在恢复数据时,需要处理备份版本与当前应用版本不同的情况:
// 带版本兼容性处理的 onRestore 实现 async onRestore(bundleVersion: BundleVersion): Promise<void> { hilog.info(DOMAIN, 'testTag', 'onRestore called, version: %{public}s', JSON.stringify(bundleVersion)); try { // 1. 获取当前应用版本 let currentVersion = this.getCurrentAppVersion(); // 2. 版本对比 if (this.isNewerVersion(bundleVersion, currentVersion)) { // 备份版本比当前应用版本新 → 数据降级处理 await this.downgradeData(bundleVersion, currentVersion); } else if (this.isOlderVersion(bundleVersion, currentVersion)) { // 备份版本比当前应用版本旧 → 数据迁移处理 await this.migrateData(bundleVersion, currentVersion); } else { // 版本相同 → 直接恢复 await Promise.resolve(); } // 3. 恢复完成后的回调 this.onRestoreCompleted(); hilog.info(DOMAIN, 'testTag', 'onRestore completed'); } catch (err) { hilog.error(DOMAIN, 'testTag', 'onRestore failed: %{public}s', JSON.stringify(err)); throw err; } } private getCurrentAppVersion(): BundleVersion { // 从 Context 获取当前应用版本号 let appInfo = this.context.applicationInfo; return { major: Math.floor(appInfo.versionCode / 1000000), minor: Math.floor((appInfo.versionCode % 1000000) / 10000), patch: Math.floor((appInfo.versionCode % 10000) / 100), build: appInfo.versionCode % 100, versionName: appInfo.versionName }; } private isNewerVersion(backup: BundleVersion, current: BundleVersion): boolean { if (backup.major > current.major) return true; if (backup.major === current.major && backup.minor > current.minor) return true; return false; } private isOlderVersion(backup: BundleVersion, current: BundleVersion): boolean { if (backup.major < current.major) return true; if (backup.major === current.major && backup.minor < current.minor) return true; return false; } private async downgradeData(backup: BundleVersion, current: BundleVersion): Promise<void> { // 备份版本更新 → 数据降级 // 例如:备份中有新版本才有的字段,需要降级处理 hilog.info(DOMAIN, 'testTag', 'Downgrading data from %{public}s to %{public}s', JSON.stringify(backup), JSON.stringify(current)); } private async migrateData(backup: BundleVersion, current: BundleVersion): Promise<void> { // 备份版本更旧 → 数据迁移 // 例如:数据库 schema 变更,需要执行 ALTER TABLE hilog.info(DOMAIN, 'testTag', 'Migrating data from %{public}s to %{public}s', JSON.stringify(backup), JSON.stringify(current)); } private onRestoreCompleted(): void { // 恢复完成后的回调,例如弹出 Toast 提示用户 hilog.info(DOMAIN, 'testTag', 'Restore completed successfully'); }4.4 版本号编码规范
小事记的versionCode为1000000,对应的版本编码规则如下:
// 版本号编码规则:MAJOR * 1000000 + MINOR * 10000 + PATCH * 100 + BUILD // 1.0.0.0 → 1000000 // 1.1.0.0 → 1010000 // 2.0.0.0 → 2000000| 版本名称 | versionCode | 分解 |
|---|---|---|
| 1.0.0.0 | 1000000 | major=1, minor=0, patch=0, build=0 |
| 1.1.0.0 | 1010000 | major=1, minor=1, patch=0, build=0 |
| 1.2.3.4 | 1020304 | major=1, minor=2, patch=3, build=4 |
五、备份与恢复的数据流
5.1 完整备份流程
[系统触发备份条件] ↓ 系统调用 BackupExtensionAbility.onBackup() ↓ onBackup 中执行预处理(数据校验、清理缓存) ↓ 系统根据 backup_config.json 的 includes 路径收集文件 ↓ 跳过 excludes 路径中的文件 ↓ 系统对文件进行加密和压缩 ↓ 将加密数据上传到云端 ↓ onBackup 返回,备份完成5.2 完整恢复流程
[用户在新设备安装应用] ↓ 系统检测到云端有备份数据 ↓ 系统调用 BackupExtensionAbility.onRestore() ↓ onRestore 接收 BundleVersion 参数 ↓ 版本对比 → 执行数据迁移或降级 ↓ 系统解密备份数据 ↓ 将数据恢复到 includes 指定的路径 ↓ onRestore 返回,恢复完成 ↓ 用户打开应用,看到已恢复的数据5.3 备份范围测试
| 测试场景 | 预期结果 | 测试方法 |
|---|---|---|
| 新增一条事件记录 | 下次备份包含该记录 | 在应用中添加事件,触发备份,恢复后检查 |
| 删除一条事件记录 | 下次备份不再包含该记录 | 删除事件,触发备份,恢复后检查 |
| 变更应用设置 | 备份包含新设置 | 修改设置项,触发备份,恢复后检查 |
| 备份文件完整性 | 恢复后数据完整无误 | 比较备份前后的数据记录总数 |
六、备份扩展的异常处理
6.1 常见异常场景
| 异常场景 | 原因 | 处理方式 |
|---|---|---|
onBackup超时 | 数据量过大,超过 30 秒 | 分片处理,或减少 includes 范围 |
| 备份文件损坏 | 存储介质故障 | 在 onRestore 中增加完整性校验 |
| 版本不兼容 | 数据库 schema 变更 | 在 onRestore 中实现数据迁移逻辑 |
| 存储空间不足 | 设备空间不足 | 系统会自动跳过备份,记录错误日志 |
6.2 超时与重试策略
// 大文件备份的超时处理 async onBackup(): Promise<void> { const BACKUP_TIMEOUT = 25000; // 25 秒超时 const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Backup timeout')), BACKUP_TIMEOUT); }); const backupPromise = this.performBackup(); try { await Promise.race([backupPromise, timeoutPromise]); hilog.info(DOMAIN, 'testTag', 'Backup completed within timeout'); } catch (err) { hilog.error(DOMAIN, 'testTag', 'Backup failed: %{public}s', JSON.stringify(err)); throw err; } } private async performBackup(): Promise<void> { // 实际的备份逻辑 await this.checkDatabaseIntegrity(); await this.cleanExpiredCache(); await this.recordBackupTimestamp(); }七、区别于 FA 模型的数据备份
7.1 模型对比
| 对比维度 | FA 模型 | Stage 模型 |
|---|---|---|
| 备份方式 | 手动处理文件 IO | BackupExtensionAbility 框架 |
| 配置方式 | 无标准化配置 | backup_config.json声明式 |
| 加密支持 | 需自行实现 | 系统自动加密 |
| 增量备份 | 不支持 | 系统支持 |
| 恢复回调 | 无 | onRestore(BundleVersion)版本感知 |
7.2 迁移建议
从 FA 模型迁移到 Stage 模型时,备份功能的迁移需要注意:
- 移除手动文件操作— 不再需要手动拷贝
databases/目录下的文件 - 添加备份配置— 创建
backup_config.json文件声明备份范围 - 实现回调方法— 在
onBackup和onRestore中添加版本兼容性处理 - 测试恢复流程— 确保数据在不同版本间可以正确恢复
八、最佳实践总结
8.1 备份配置的推荐策略
{ "allowToBackup": true, "includes": [ "data/storage/el2/database/", // 包含用户数据库 "data/storage/el2/base/preferences/", // 包含偏好设置 "data/storage/el2/base/haps/entry/files/" // 包含用户生成的文件 ], "excludes": [ "data/storage/el2/base/cache/", // 排除缓存 "data/storage/el2/base/temp/", // 排除临时文件 "data/storage/el1/base/preferences/" // 排除设备级配置 ] }8.2 onBackup 中的注意事项
- 不要执行耗时操作— 系统对
onBackup有超时限制(30 秒) - 不要修改用户数据—
onBackup应该只读取数据,不修改数据 - 异常必须抛出— 如果备份失败,应该抛出异常让系统感知
- 避免网络请求— 备份时的网络状态不可预测
8.3 onRestore 中的注意事项
- 版本号必须校验— 确保备份数据与当前应用版本兼容
- 数据迁移必须幂等— 多次恢复同一个备份,结果应该一致
- 恢复失败要回滚— 如果恢复过程中出现错误,应该回滚到初始状态
- 用户数据优先— 恢复时不要覆盖用户当前已有的新数据
总结
本文从xiaoshiji_ohos_app项目的EntryBackupAbility.ets出发,深入解析了 HarmonyOSBackupExtensionAbility的注册机制、生命周期回调、备份配置文件和版本管理策略。核心要点如下:
- 注册机制:在
module.json5中通过extensionAbilities注册,type为backup,通过metadata引用backup_config.json配置文件 - 备份配置:通过
backup_config.json的includes/excludes声明式指定备份范围,系统自动处理文件加密和传输 - onBackup:系统在充电+Wi-Fi+空闲时自动触发,开发者可在此执行数据校验和缓存清理
- onRestore:接收
BundleVersion参数,需要实现版本兼容性处理(数据迁移/降级) - 版本管理:
versionCode编码规范(MAJOR1000000 + MINOR10000 + PATCH*100 + BUILD)确保版本号能精确比较
下一篇文章将深入解析应用包结构:HAP/HSP/HAR 的三层架构与 deliveryWithInstall 策略。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- 小事记项目源码:xiaoshiji_ohos_app
- 官方文档 - 应用模型:application-models.md
- 官方文档 - 包结构:application-package-structure-stage.md
- 官方文档 - 包开发:application-package-dev.md
- 官方文档 - 包基础:application-package-fundamentals.md
- 官方文档 - 安装卸载:application-package-install-uninstall.md
- 官方文档 - 配置文件:application-configuration-file-stage.md
- 开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net