前言
在 ArkUI 状态管理体系中,数据存储被分为三个层级:AppStorage(应用级内存存储)、PersistentStorage(持久化键值存储)、LocalStorage(页面级内存存储)。这三个层级各有不同的作用范围、生命周期和适用场景,合理选择它们直接决定了应用的数据一致性、持久化能力和性能表现。
本文将以开源鸿蒙笔友通信应用 xiexin 的DataStore.ets为蓝本,详细剖析这三个存储层级的语义、差异,以及它们在实际开发中的选型策略。
提示:本文假设你已经了解 ArkUI 状态管理的基本概念。如果还不熟悉,建议先阅读前十三篇文章。
一、三级存储总览
1.1 三个层级的核心差异
| 维度 | AppStorage | PersistentStorage | LocalStorage |
|---|---|---|---|
| 存储位置 | 应用进程内存 | 磁盘文件 | 页面实例内存 |
| 生命周期 | 应用进程存在 | 持久化到存储 | 页面实例存在 |
| 作用范围 | 整个应用 | 整个应用 | 单个页面及子组件 |
| 数据持久化 | 否(进程重启丢失) | 是(重启后恢复) | 否(页面销毁丢失) |
| 访问方式 | @StorageLink/@StorageProp | PersistentStorage.persistProp | LocalStorage实例 |
| 典型用途 | 用户信息、列表数据 | 用户偏好、登录态 | 页面内临时状态 |
1.2 xiexin 当前的使用情况
xiexin 当前只使用了AppStorage,没有使用 PersistentStorage 和 LocalStorage:
// DataStore.ets — 仅使用 AppStorage static initializeData(): void { AppStorage.setOrCreate<PenPal[]>('penPals', penPals); AppStorage.setOrCreate<Letter[]>('letters', letters); AppStorage.setOrCreate<UserProfile>('userProfile', profile); AppStorage.setOrCreate<WriteStats>('writeStats', stats); AppStorage.setOrCreate<InviteRecord[]>('invites', []); AppStorage.setOrCreate<boolean>('hasSeenSplash', false); AppStorage.setOrCreate<number>('currentTab', 0); }这种设计的局限是:应用重启后,所有数据丢失。xiexin 当前作为“纯演示“应用可以接受,但如果要落地为真实产品,必须引入 PersistentStorage 实现数据持久化。
二、AppStorage:应用级内存存储
2.1 AppStorage 的核心特性
AppStorage是应用启动时创建的单例对象,在应用进程运行期间一直存在。
// 写入 AppStorage.setOrCreate('key', 'value'); // 不覆盖已有值 AppStorage.set('key', 'value'); // 强制覆盖 // 读取 const value = AppStorage.get('key'); // 不存在返回 undefined // 删除 AppStorage.delete('key'); // 清空 AppStorage.clear();2.2 AppStorage 的装饰器绑定
// @StorageLink:双向绑定(组件修改 → AppStorage 更新) @StorageLink('penPals') penPals: PenPal[] = []; // @StorageProp:单向绑定(AppStorage → 组件,组件修改不更新 AppStorage) @StorageProp('penPals') penPals: PenPal[] = [];2.3 AppStorage 的最佳实践
| 适用场景 | 不适用场景 |
|---|---|
| 页面间共享的数据 | 页面内临时状态 |
| 从网络加载的列表数据 | 用户偏好设置 |
| 需要跨组件同步的数据 | 磁盘 IO 密集型数据 |
| 数据量大但不频繁修改 | 需要持久化的数据 |
三、PersistentStorage:持久化键值存储
3.1 PersistentStorage 的核心特性
PersistentStorage提供持久化键值存储能力,数据会写入磁盘,应用重启后可以恢复。
import { PersistentStorage } from '@kit.ArkUI'; // 持久化一个 key,并关联到 AppStorage PersistentStorage.persistProp('hasSeenSplash', false); // 删除持久化 key PersistentStorage.deleteProp('hasSeenSplash'); // 持久化后,通过 AppStorage 访问 const hasSeenSplash = AppStorage.get<boolean>('hasSeenSplash') ?? false;3.2 PersistentStorage 的工作原理
graph LR subgraph 内存 AS[AppStorage: hasSeenSplash = true] end subgraph 磁盘 PS[PersistentStorage] end AS <-->|持久化同步| PS当PersistentStorage.persistProp('hasSeenSplash', false)被调用后:
- 键
'hasSeenSplash'被注册到 AppStorage - 如果磁盘中已有
hasSeenSplash的值,从磁盘恢复 - 如果磁盘中还没有,用默认值
false初始化 - 后续
AppStorage.set('hasSeenSplash', true)会同步写入磁盘 - 应用重启后,
AppStorage.get('hasSeenSplash')返回true
3.3 PersistentStorage 的约束
| 约束 | 说明 |
|---|---|
| 值类型限制 | 仅支持number、string、boolean、Object及其数组 |
| 不支持 class 实例 | 不能持久化@Observed类的实例 |
| 最大 key 数量 | 无明确限制,但建议不超过 100 个 |
| 同步写入 | 持久化操作是同步的 |
3.4 在 xiexin 中引入 PersistentStorage
// 改造后的 DataStore.ets import { PersistentStorage } from '@kit.ArkUI'; export class DataStore { static initializeData(): void { // 持久化的状态:用 persistProp 注册 PersistentStorage.persistProp('hasSeenSplash', false); PersistentStorage.persistProp('currentTab', 0); PersistentStorage.persistProp('userProfile', JSON.stringify(new UserProfile())); // 非持久化的状态:用 setOrCreate const penPals: PenPal[] = DataStore.createMockPenPals(); AppStorage.setOrCreate<PenPal[]>('penPals', penPals); const letters: Letter[] = DataStore.createMockLetters(); AppStorage.setOrCreate<Letter[]>('letters', letters); } }四、LocalStorage:页面级内存存储
4.1 LocalStorage 的核心特性
LocalStorage是页面级的内存存储,绑定到页面实例的生命周期。
// 创建 LocalStorage 实例 const pageStorage = new LocalStorage(); pageStorage.setOrCreate('pageTitle', '写信'); // 在 @Entry 组件中绑定 @Entry(pageStorage) @Component struct ComposePage { @LocalStorageProp('pageTitle') pageTitle: string = ''; @LocalStorageLink('draft') draft: string = ''; }4.2 LocalStorage 的装饰器绑定
// @LocalStorageProp:单向绑定 @LocalStorageProp('pageTitle') pageTitle: string = ''; // @LocalStorageLink:双向绑定 @LocalStorageLink('draft') draft: string = '';4.3 LocalStorage 的典型应用场景
// 场景:页面内多 Tab 共享状态 const pageStorage = new LocalStorage(); pageStorage.setOrCreate('currentTab', 0); pageStorage.setOrCreate('searchQuery', ''); // 页面组件 @Entry(pageStorage) @Component struct ComplexPage { @LocalStorageProp('currentTab') currentTab: number = 0; build() { Tabs({ index: this.currentTab }) { TabContent() { SearchTab() } TabContent() { BrowseTab() } } } } // 子组件共享同一个 LocalStorage @Component struct SearchTab { @LocalStorageLink('searchQuery') searchQuery: string = ''; build() { TextInput({ text: this.searchQuery }) .onChange((value: string) => { this.searchQuery = value; // 子组件修改,父组件和其他子组件自动同步 }) } }五、三级存储选型决策树
数据是否需要持久化? ├── 是 → PersistentStorage │ └── 数据是否需要跨页面共享? │ ├── 是 → 使用 persistProp 注册到 AppStorage │ └── 否 → 使用 AppStorage(非持久化)或持久化到文件 └── 否 → 数据是否需要跨页面共享? ├── 是 → AppStorage └── 否 → 数据是否需要跨子组件共享? ├── 是 → LocalStorage └── 否 → @State 组件私有状态六、xiexin 的存储层改造建议
6.1 当前状态分析
xiexin 当前的数据存储需求:
| 数据 | 当前存储 | 是否需要持久化 | 是否跨页面共享 | 建议方案 |
|---|---|---|---|---|
| penPals(笔友列表) | AppStorage | 是 | 是 | PersistentStorage(序列化) |
| letters(信件列表) | AppStorage | 是 | 是 | PersistentStorage(序列化) |
| userProfile(用户信息) | AppStorage | 是 | 是 | PersistentStorage(JSON) |
| writeStats(统计数据) | AppStorage | 是 | 是 | 从 letters 实时计算 |
| invites(邀请记录) | AppStorage | 是 | 是 | PersistentStorage(序列化) |
| hasSeenSplash(引导状态) | AppStorage | 是 | 否 | PersistentStorage |
| currentTab(Tab 索引) | AppStorage | 否 | 是 | AppStorage |
6.2 改造后的 DataStore
import { PersistentStorage } from '@kit.ArkUI'; export class DataStore { static initializeData(): void { // 1. 持久化的原子状态 PersistentStorage.persistProp('hasSeenSplash', false); PersistentStorage.persistProp('currentTab', 0); // 2. 持久化的复杂对象(序列化存储) const savedProfile = AppStorage.get<string>('saved_profile'); if (!savedProfile) { const profile = DataStore.createMockProfile(); PersistentStorage.persistProp('saved_profile', JSON.stringify(profile)); } // 反序列化回 AppStorage const profileStr = AppStorage.get<string>('saved_profile') ?? '{}'; const profile: UserProfile = JSON.parse(profileStr); AppStorage.setOrCreate<UserProfile>('userProfile', profile); // 3. 非持久化的运行时数据 const penPals: PenPal[] = DataStore.createMockPenPals(); AppStorage.setOrCreate<PenPal[]>('penPals', penPals); const letters: Letter[] = DataStore.createMockLetters(); AppStorage.setOrCreate<Letter[]>('letters', letters); // 4. 从 letters 实时计算统计数据 const stats = DataStore.computeStats(letters); AppStorage.setOrCreate<WriteStats>('writeStats', stats); } static addLetter(letter: Letter): void { // 原有逻辑不变 const letters: Letter[] = AppStorage.get<Letter[]>('letters') ?? []; letter.id = letters.length > 0 ? Math.max(...letters.map((l: Letter) => l.id)) + 1 : 1; letters.unshift(letter); AppStorage.set<Letter[]>('letters', letters); // 同步更新笔友统计 const penPals: PenPal[] = AppStorage.get<PenPal[]>('penPals') ?? []; const idx = penPals.findIndex((p: PenPal) => p.id === letter.penPalId); if (idx >= 0) { penPals[idx].totalLetters += 1; penPals[idx].lastLetterAt = letter.createdAt; penPals[idx].lastLetterPreview = letter.body.substring(0, 30); penPals[idx].lastLetterStatus = LetterStatus.WAITING_OTHER; penPals[idx].isLastLetterSender = true; AppStorage.set<PenPal[]>('penPals', penPals); } } }七、从 AppStorage 到 PersistentStorage 的迁移路径
7.1 迁移步骤
- 识别持久化需求:确定哪些数据需要持久化
- 注册 PersistentStorage key:用
persistProp注册 - 序列化复杂对象:
@Observed类实例需要序列化 - 迁移数据:把 AppStorage 中的数据同步到 PersistentStorage
- 测试:验证数据持久化是否正确
7.2 序列化与反序列化
// 写入时:序列化 function savePenPals(penPals: PenPal[]): void { const json = JSON.stringify(penPals); AppStorage.set<string>('saved_penPals', json); } // 读取时:反序列化 function loadPenPals(): PenPal[] { const json = AppStorage.get<string>('saved_penPals') ?? '[]'; const data = JSON.parse(json) as Array<Record<string, Object>>; return data.map((item) => { const pal = new PenPal(); Object.assign(pal, item); return pal; }); }八、LocalStorage 在 xiexin 中的应用
8.1 场景:ComposePage 的草稿管理
xiexin 的 ComposePage 有 14 个@State变量,如果使用 LocalStorage 来管理草稿,可以简化代码:
// 创建页面级 LocalStorage const composeStorage = new LocalStorage(); composeStorage.setOrCreate('penPalId', -1); composeStorage.setOrCreate('body', ''); composeStorage.setOrCreate('honorific', '尊敬的'); composeStorage.setOrCreate('greeting', '您好!'); composeStorage.setOrCreate('closing', '此致\n敬礼!'); composeStorage.setOrCreate('paperStyle', PaperStyle.PLAIN); composeStorage.setOrCreate('wordCount', 0); @Entry(composeStorage) @Component struct ComposePage { @LocalStorageProp('penPalId') selectedPenPalId: number = -1; @LocalStorageProp('body') body: string = ''; @LocalStorageProp('honorific') honorific: string = '尊敬的'; @LocalStorageProp('greeting') greeting: string = '您好!'; @LocalStorageProp('closing') closing: string = '此致\n敬礼!'; @LocalStorageProp('paperStyle') paperStyle: PaperStyle = PaperStyle.PLAIN; @LocalStorageProp('wordCount') wordCount: number = 0; // ... 其他逻辑 }8.2 LocalStorage 与 AppStorage 的配合
// 页面销毁时,保存草稿到 AppStorage aboutToDisappear(): void { if (this.body.length > 50) { AppStorage.setOrCreate('draft', { penPalId: this.selectedPenPalId, body: this.body, honorific: this.honorific, greeting: this.greeting, closing: this.closing, paperStyle: this.paperStyle, savedAt: Date.now() }); } }九、三级存储的性能对比
9.1 读写性能
| 存储层级 | 读性能 | 写性能 | 说明 |
|---|---|---|---|
| AppStorage | 纳秒级 | 纳秒级 | 内存操作,极快 |
| LocalStorage | 纳秒级 | 纳秒级 | 内存操作,极快 |
| PersistentStorage | 微秒级 | 微秒级 | 磁盘 IO,较慢 |
9.2 内存占用
- AppStorage/LocalStorage:数据在内存中,占用应用进程内存
- PersistentStorage:数据在磁盘上,仅当被访问时加载到内存
9.3 选型建议
// 高频读写、不持久化 → AppStorage AppStorage.set('currentTab', index); // 低频读写、需要持久化 → PersistentStorage PersistentStorage.persistProp('hasSeenSplash', false); // 页面内共享、页面销毁可丢弃 → LocalStorage const pageStorage = new LocalStorage(); pageStorage.setOrCreate('draft', '');十、从 xiexin 看存储架构设计
xiexin 当前的存储架构(仅 AppStorage)适合演示和原型阶段。如果要落地为真实应用,建议按以下优先级改造:
- 短期(1-2 周):引入 PersistentStorage,持久化
hasSeenSplash和currentTab - 中期(1-2 月):引入 RDB 数据库,替代数组存储的笔友和信件数据
- 长期(3-6 月):引入分布式数据对象,实现跨设备数据同步
总结
本文详细剖析了 HarmonyOS ArkUI 的 AppStorage/PersistentStorage/LocalStorage 三级存储的语义、差异和选型策略。xiexin 当前仅使用 AppStorage 的架构适合演示阶段,但通过引入 PersistentStorage 和 LocalStorage,可以显著提升数据持久化能力和页面隔离性。
理解三级存储的关键是把握“三个维度“:生命周期(进程/持久化/页面)、作用范围(应用/页面)、数据流向(双向/单向)。掌握了这三个维度,就能在不同场景中选出最合适的存储方案。
下一篇文章我们将深入 ForEach 列表渲染,剖析键值策略与 Diff 算法的实现细节。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源
- 开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
- HarmonyOS AppStorage 全局状态:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-appstorage
- HarmonyOS PersistentStorage 持久化:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-persiststorage
- HarmonyOS LocalStorage 页面级存储:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-localstorage
- HarmonyOS 状态管理概述:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-management-overview
- HarmonyOS 应用数据持久化:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-data-persistence
- HarmonyOS 通过用户首选项实现持久化:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/data-persistence-by-preferences
- HarmonyOS 分布式数据对象:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/data-sync-of-distributed-data-object
- HarmonyOS 关系型数据库:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/data-persistence-by-rdb-store