HarmonyOS 5 数据持久化:状态持久化 (PersistentStorage)
摘要:本文介绍了HarmonyOS 5中的状态持久化工具PersistentStorage,它能自动将AppStorage中的变量同步到磁盘,实现数据持久化。通过@StorageLink装饰器,开发者可以像操作普通变量一样实现数据自动保存和恢复。文章以搜索历史功能为例,演示了如何通过PersistentStorage.persistProp()初始化持久化属性,并利用双向绑定实现无感数据存储。最后
HarmonyOS 5 数据持久化:状态持久化 (PersistentStorage)
大家好,我是不想掉发的鸿蒙开发工程师 城中的雾。在上一期中,我们学会了用首选项(Preferences)手动存取数据。虽然封装了工具类,但每次修改数据还得调用 PreferenceUtil.put,是不是还是觉得有点麻烦,有没有一种办法,能让我像操作普通变量一样:
this.myVar = 100;
然后系统就自动把它存进硬盘;下次打开 App,变量自动变回 100?有!这就是鸿蒙的 PersistentStorage。今天我们来聊聊这个状态变量持久化。
1. 它是谁?AppStorage 的“影子”
要理解 PersistentStorage,必须先理解 AppStorage。
- AppStorage:是应用全局的内存状态管理器。数据存在内存里,杀进程就没了。
- PersistentStorage:是 AppStorage 的持久化“插件”。它将磁盘文件与 AppStorage 中的特定属性建立双向绑定。
工作原理 (双向绑定):
UI组件 (@StorageLink) <==> AppStorage (内存) <==> PersistentStorage (磁盘)
当你修改 UI 绑定的 @StorageLink 变量时 -> AppStorage 更新 -> PersistentStorage 自动写入磁盘。
当你重启 App 时 -> PersistentStorage 读取磁盘 -> 填充 AppStorage -> UI 自动显示旧数据。
2. 核心 API:persistProp
使用 PersistentStorage 极其简单,核心方法只有一个:persistProp。
// 将 'is_night_mode' 属性持久化,默认值为 false
// 这行代码必须在 UI 使用该属性之前调用!
PersistentStorage.persistProp('is_night_mode', false);
执行流程:
- 检查磁盘里有没有
is_night_mode? - 有:把磁盘里的值读出来,覆盖到 AppStorage 中。
- 没有:把默认值
false写入 AppStorage。
3. 实战:搜索历史记录
我们来实现一个经典的“搜索历史”功能。用户输入关键词搜索,历史记录自动保存;杀掉 App 再打开,历史记录还在。
第一步:初始化 (EntryAbility)
关键点:persistProp 最好放在 EntryAbility 的 onWindowStageCreate 中,确保初始化成功,持久化链接已经建立。
// EntryAbility.ets
import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowClass = windowStage.getMainWindowSync();
windowStage2 = windowStage
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
// 初始化持久化属性
// 'search_history' 是 key,[] 是默认值
PersistentStorage.persistProp('search_history', []);
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
});
}
}
第二步:UI 实现 (SearchPage.ets)
在页面中,我们只需要使用 @StorageLink 装饰器。完全不需要写任何“保存”或“读取”的代码!

@Entry
@Component
struct SearchPage {
@State inputText: string = '';
// 核心
// 使用 @StorageLink 双向绑定全局状态
// 当 this.history 发生变化时,UI 会刷新,且 PersistentStorage 会自动将其写入磁盘
@StorageLink('search_history') history: string[] = [];
build() {
Column() {
// 搜索框区域
Row() {
TextInput({ placeholder: '请输入搜索内容', text: this.inputText })
.layoutWeight(1)
.onChange((val) => this.inputText = val)
.onSubmit(() => this.doSearch()) // 回车搜索
Button('搜索')
.onClick(() => this.doSearch())
.margin({ left: 10 })
}
.width('100%')
.padding(10)
.backgroundColor('#F0F0F0')
// 历史记录标题
Row() {
Text('历史记录').fontSize(14).fontColor('#999')
Blank()
// 清空按钮
Text('清空')
.fontSize(14)
.fontColor('blue')
.onClick(() => {
// 直接清空数组,磁盘也会自动清空!
this.history = [];
})
}
.width('100%')
.padding(10)
// 历史列表
List() {
ForEach(this.history, (item: string) => {
ListItem() {
Text(item)
.fontSize(16)
.padding(10)
.width('100%')
.border({ width: { bottom: 1 }, color: '#EEE' })
}
})
}
.layoutWeight(1)
.width('100%')
}
.height('100%')
}
// 执行搜索逻辑
doSearch() {
if (this.inputText.trim() === '') return;
// 1. 简单的去重逻辑:如果存在先删除
const index = this.history.indexOf(this.inputText);
if (index !== -1) {
this.history.splice(index, 1);
}
// 2. 插入到头部
this.history.unshift(this.inputText);
// 3. 限制长度(比如只存 10 条)
if (this.history.length > 10) {
this.history.pop();
}
// 清空输入框
this.inputText = '';
// 【注意】:对于数组和对象,如果是通过方法修改内部元素(如 push/splice),
// 必须确保 @StorageLink 能感知到。
// 在 ArkUI 中,Array 的方法通常会被拦截并触发更新,
// 但为了保险,有时需要重新赋值:this.history = [...this.history];
// 不过在 @StorageLink 中,直接调用变异方法通常是生效的。
}
}
体验效果
- 输入 “鸿蒙”,点击搜索。列表出现 “鸿蒙”。
- 输入 “ArkTS”,点击搜索。列表出现 “ArkTS”, “鸿蒙”。
- 强杀 App 进程。
- 重新打开 App。你会发现列表里依然躺着 “ArkTS” 和 “鸿蒙”。
全程没有调用过一次 save 或 flush,调用存储也很便捷
4. 避坑指南:魔法的代价
虽然 PersistentStorage 很好用,但它不是万能的,甚至有很多“坑”。
坑 1:不要存复杂对象
它支持 number, string, boolean, enum 等基础类型。
如果你存一个复杂的 class 实例或者嵌套很深的 JSON:
- 序列化性能差:每次修改都会触发 JSON 序列化和磁盘写入,导致 UI 掉帧。
- 状态丢失:存进去的是 Object,读出来可能就丢失了类的方法(Prototype)。
坑 2:不要绑定高频变化的数据
错误示范:
// 监听滚动位置
@StorageLink('scroll_y') scrollY: number = 0;
onScroll((y) => {
this.scrollY = y; // 每一帧都在疯狂写磁盘!!手机会发烫卡顿。
})
坑 3:初始化顺序
一定要先调用 PersistentStorage.persistProp,再使用 @StorageLink。
如果在 persistProp 之前页面就已经加载并使用了 @StorageLink,那么页面可能会显示默认值,而磁盘里的旧数据会被覆盖或忽略。
最佳实践:始终在 EntryAbility.onWindowStageCreate里做持久化初始化。
坑 4:PersistentStorage vs Preferences
| 特性 | 首选项 (Preferences) | 状态持久化 (PersistentStorage) |
|---|---|---|
| 操作方式 | 手动 (get/put) | 自动 (绑定变量) |
| 适用场景 | 逻辑配置、非 UI 强关联数据 | UI 状态、用户输入历史、设置开关 |
| 性能 | 较好 (手动控制刷盘) | 一般 (变化即刷盘) |
| 灵活性 | 高 (可在 Utils 使用) | 低 (依赖 AppStorage) |
5. 总结
- PersistentStorage 是 AppStorage 的持久化扩展,实现了内存与磁盘的双向绑定。
- 使用
@StorageLink可以让你像操作普通变量一样操作磁盘数据。 - 切记:不要存大数据,不要存高频变化的数据,一定要在 Ability 中尽早初始化。
下一期预告:
如果我要存 1000 条聊天记录,或者要存一个包含“姓名、年龄、头像、简介”的用户列表,首选项和 PersistentStorage 都不好使了(性能太差,且无法查询)。
这时候,我们需要使用——关系型数据库 (RelationalStore)。
下一篇,我们在手机里塞个 Excel,学学 SQL 怎么写。
📚 充电时间
如果有想加入鸿蒙生态的大佬们,快来加入鸿蒙认证吧!初高级证书还没获取的,点这里:
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信中提出,非常感谢您的支持。
更多推荐


所有评论(0)