HarmonyOS 多设备短视频开发 :25 — 数据持久化 Preferences 与状态恢复
25 — 数据持久化 Preferences 与状态恢复
一、引言
内存态状态(AppStorageV2、@Local、@Provider/@Consumer)随进程消亡而丢失。短视频应用中,"上次看到哪个视频、进度到哪里、停留在哪个页签"这类轻量配置如果每次启动都重置,体验会大打折扣。HarmonyOS 提供 @ohos.data.preferences 键值型持久化存储,以轻量、同步友好的方式解决这一问题。本文讲解 Preferences 的读写模型、批量操作与文件管理,并结合 multi-short-video 的场景给出"内存状态 ↔ 持久化"联动的落地设计。

二、Preferences 简介与适用场景
Preferences 以"键-值"对形式存储,值为 number、string、boolean、Array 等简单类型,适合保存用户偏好与轻量状态,不适合大数据与复杂查询。项目中的典型可持久化项:
| 存储项 | 类型 | 来源状态 | 用途 |
|---|---|---|---|
| videoIndex | number | AdaptiveVideo.curIndex | 恢复上次观看的视频下标 |
| videoPosition | number | AdaptiveAVPlayer.currentProgress | 恢复播放位置 |
| tabIndex | number | Index.subTabIndex | 恢复底部页签 |
| lastWidthBp | string | WindowInfo.widthBp | 断点偏好记录 |
对比关系型数据库(RelationalStore):Preferences 适合"几十个键、简单读写";RDB 适合结构化、可查询、量大(如评论缓存)的数据。两者选型可归纳为:
| 维度 | Preferences | RelationalStore |
|---|---|---|
| 数据模型 | 键值对 | 表 + 行 + 列 |
| 查询能力 | 仅按 key | SQL 谓词、排序、分页 |
| 数据规模 | 少量轻量 | 大量结构化 |
| 典型场景 | 用户偏好、播放位置、页签索引 | 评论缓存、作品列表、消息 |
| 复杂度 | 低 | 高(建库建表、事务) |
本项目评论、作品列表均为内存模拟数据,因此持久化层以 Preferences 为主即可;若后续引入离线评论缓存,再升级为 RDB。
三、Preferences 的读取与写入
获取 Preferences 实例需要上下文与文件名:
// 项目风格的 Preferences 封装(建议实现,仿照 WindowUtil 单例)
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
const STORE_NAME = 'msv_user_config';
export class PreferenceUtil {
private static instance: PreferenceUtil | undefined = undefined;
private prefs?: preferences.Preferences;
static getInstance(): PreferenceUtil {
if (PreferenceUtil.instance === undefined) {
PreferenceUtil.instance = new PreferenceUtil();
}
return PreferenceUtil.instance;
}
async init(context: common.UIAbilityContext): Promise<void> {
this.prefs = await preferences.getPreferences(context, STORE_NAME);
}
async put(key: string, value: preferences.ValueType): Promise<void> {
await this.prefs?.put(key, value);
await this.prefs?.flush(); // 同步到磁盘,保证落盘
}
async get(key: string, defaultValue: preferences.ValueType): Promise<preferences.ValueType> {
return await this.prefs?.get(key, defaultValue) ?? defaultValue;
}
}
要点:getPreferences 以 (context, name) 创建/打开实例;put 修改内存缓存,flush() 才真正落盘;进程被杀前建议在 Ability 的 onBackground/onDestroy 中执行一次 flush,避免丢失。写入读取均为异步接口,注意用 await 串行化。
接口还支持回调风格与 Promise 风格两种调用方式,统一使用 Promise + async/await 更契合工程风格;getPreferences 返回的 Preferences 实例在应用退出后自动清理句柄,无需手动 close。此外,getAll() 返回的键值对象可直接用于状态恢复前的整体校验,避免逐键读取的开销。
四、批量操作与文件管理
频繁单键 flush 会触发多次 IO,批量场景应合并写入:
// 批量保存:一次 flush 提交多条
async savePlayState(index: number, position: number): Promise<void> {
await this.prefs?.put('videoIndex', index);
await this.prefs?.put('videoPosition', position);
await this.prefs?.flush(); // 统一落盘一次
}
其他常用接口:getAll() 读取全部键值;delete(key) 删除单键;clear() 清空当前 Preferences 文件;on('change')/off('change') 监听文件变化(多实例场景同步)。每个应用可创建多个 Preferences 文件,建议按业务域划分(如 msv_user_config 存用户偏好、msv_play_state 存播放状态),避免单文件膨胀;文件过时可用 deletePreferences(context, name) 删除。
五、页面状态恢复实战:视频位置与页签
将持久化与 V2 状态管理联动,可实现"启动即恢复"。以视频页为例,约 500ms 的 timeUpdate 回调天然是"节流后的保存时机":
// 扩展示例:在 AdaptiveVideo.ets / AdaptiveAVPlayer.ets 中接入持久化(项目风格)
@ComponentV2
export struct AdaptiveAVPlayer {
@Consumer('currentTime') currentProgress: number = 0;
@Consumer('duration') duration: number = 0;
aboutToAppear(): void {
// 恢复上次观看位置
PreferenceUtil.getInstance().get('videoIndex', 0).then((v) => {
// 结合列表长度校验后回写父组件 curIndex
});
}
private onTimeUpdateFunction: (updateTime: number) => void = (updateTime: number) => {
if (this.currentIndex === this.index) {
this.currentProgress = updateTime;
PreferenceUtil.getInstance().put('videoPosition', updateTime); // 定时保存进度
}
}
}
恢复时须校验数据有效性(index 是否越界、position 是否小于 duration),再回写 @Param/@Consumer 状态;页签恢复同理,Index.ets 的 subTabIndex 在 aboutToAppear 中读取并赋值,即可保留"上次停留在我的页签"的体验。建议在 Ability 的 onBackground 回调中调用一次全局 flush,兜底异步落盘。
六、Preferences 的限制与选型
- 容量与规模限制:Preferences 适合少量键值,量大(千级以上)应改用 RDB。
- 类型限制:值为基本类型与数组,存对象需 JSON.stringify/parse;注意 ArkTS 的序列化约束。
- 异步模型:读写均异步,读取未完成时组件已渲染会导致短暂缺省,需要默认值兜底。
- 与 AppStorageV2 的分工:AppStorageV2 管"进程内全局内存态",Preferences 管"跨进程启动的持久态",二者通过启动时的"读→写回内存"和运行时的"内存→定时落盘"衔接,切勿把持久化逻辑散落在组件内。
- 版本与迁移:存储文件格式随版本演进需要兼容策略,建议在键名前加版本号或维护一份 schemaVersion 键,升级时据此做一次迁移写入,避免新旧版本字段不一致导致恢复异常。
- 写入时机与功耗:进度类高频写入会持续唤醒磁盘 IO,除按业务节流外,还可在页面可见性变化(onPageHide)与 Ability 退后台时兜底 flush,平衡可靠性与功耗。
七、总结与最佳实践
- 明确持久化边界:用户偏好、播放位置、页签索引这类轻量状态用 Preferences,结构化大数据用 RDB。
- 统一封装单例(PreferenceUtil),所有读写走封装接口,避免散落 getPreferences 调用。
- 批量写入 + 定时 flush:timeUpdate 高频回调中只 put 不频繁 flush,在 onBackground 统一落盘。
- 恢复状态必须校验(越界、过期),并在 aboutToAppear 阶段尽早回写内存状态。
- 键名常量化管理,按业务域拆分存储文件,配合 AppStorageV2 形成"内存 ↔ 磁盘"闭环。
更多推荐




所有评论(0)