💾 零基础学 ArkUI 数据持久化(专题三):5 种存储方案深度对比
博主说: 用户的数据|第一行代码|App 重启就不见了?那这个 App 基本就废了。数据持久化是每个 App 的刚需------不管是用户设置、笔记内容、缓存图片还是离线数据库。HarmonyOS 提供了 5 种持久化方案,今天一次讲清楚「什么时候用哪个」以及「怎么写」。
📊 5 种方案总览
| 方案 | 数据类型 | 读写速度 | 存储限制 | 适用场景 |
|---|---|---|---|---|
| 🥇 PersistentStorage | 简单 KV(字符串/数字/布尔) | 极快 | 少量 | 用户设置、主题、开关状态 |
| 🥈 preferences | KV 键值对 | 快 | 中等 | 笔记内容、草稿、配置 |
| 🥉 SQLite (relationalStore) | 结构化关系数据 | 中 | 大 | 通讯录、订单、复杂查询 |
| ④ fileIo 文件存储 | 二进制/文本文件 | 中 | 大 | 图片缓存、日志、导出文件 |
| ⑤ 分布式数据库 | 跨设备同步数据 | 慢 | 大 | 多设备协同、云同步 |
⚙️ 运行环境
| 项目 | 要求 |
|---|---|
| DevEco Studio | 5.0.3.800+ |
| HarmonyOS SDK | API 12+ |
🛠️ 5 种方案代码实战
🎯 方案 1:PersistentStorage --- 最简单,适合开关/主题
typescript
@Entry
@Component
struct Demo_PersistentStorage {
// PersistentStorage 自动将变量同步到磁盘
@StorageLink('isDarkMode') isDarkMode: boolean = false;
@StorageLink('themeColor') themeColor: string = '#007AFF';
@StorageLink('fontSize') fontSize: number = 16;
build() {
Column() {
Text('App 设置').fontSize(24).fontWeight(FontWeight.Bold)
Row() {
Text('暗黑模式')
Toggle({ type: ToggleType.Switch, isOn: this.isDarkMode })
.onChange((v: boolean) => {
this.isDarkMode = v; // 自动持久化!
})
}.justifyContent(FlexAlign.SpaceBetween).width('90%').padding(12)
Row() {
Text('主题色')
Select([{ value: '#007AFF' }, { value: '#FF3B30' }, { value: '#34C759' }])
.value(this.themeColor)
.onSelect((_, val: string) => { this.themeColor = val; })
}.width('90%').padding(12)
Row() {
Text('字体大小')
Slider({ value: this.fontSize, min: 12, max: 24, step: 2 })
.onChange((v: number) => { this.fontSize = v; })
}.width('90%').padding(12)
Text('💡 关闭 App 重新打开,所有设置依然保留!')
.fontSize(14).fontColor('#999').margin({ top: 20 })
}
.width('100%').padding(16)
}
}
原理: @StorageLink 在 App 启动时自动从 PersistentStorage 加载数据,修改时自动写回------零手写存储代码。
🎯 方案 2:preferences --- 手动 KV 存储,灵活可控
typescript
import preferences from '@ohos.data.preferences';
@Entry
@Component
struct Demo_Preferences {
@State notes: string[] = [];
private pref!: preferences.Preferences;
aboutToAppear() {
this.initPref();
}
async initPref() {
const ctx = getContext(this);
this.pref = await preferences.getPreferences(ctx, 'note_store');
await this.loadNotes();
}
async loadNotes() {
const json = this.pref.get('notes', '[]');
this.notes = JSON.parse(json as string);
}
async saveNotes() {
await this.pref.put('notes', JSON.stringify(this.notes));
await this.pref.flush(); // 立即写盘
}
async addNote(text: string) {
this.notes.unshift(text);
await this.saveNotes();
}
async deleteNote(idx: number) {
this.notes.splice(idx, 1);
await this.saveNotes();
}
build() {
Column() {
Button('➕ 添加笔记').onClick(() => { this.addNote('新笔记 ' + Date.now()); })
List() {
ForEach(this.notes, (note: string, idx: number) => {
ListItem() {
Row() {
Text(note).layoutWeight(1)
Button('✕').fontColor('#FF3B30').backgroundColor('transparent')
.onClick(() => { this.deleteNote(idx as number); })
}.padding(12)
}
})
}.layoutWeight(1).width('100%')
}.width('100%').height('100%').padding(16)
}
}
🎯 方案 3:SQLite (relationalStore) --- 结构化数据搜索引擎
typescript
import relationalStore from '@ohos.data.relationalStore';
@Entry
@Component
struct Demo_SQLite {
private store!: relationalStore.RdbStore;
@State users: { id: number; name: string; age: number }[] = [];
aboutToAppear() {
this.initDB();
}
async initDB() {
const config = {
name: 'myapp.db',
securityLevel: relationalStore.SecurityLevel.S1
};
this.store = await relationalStore.getRdbStore(getContext(this), config);
// 建表
await this.store.executeSql(
'CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)'
);
await this.loadUsers();
}
async addUser(name: string, age: number) {
await this.store.insert('user', { name, age });
await this.loadUsers();
}
async loadUsers() {
const predicates = new relationalStore.RdbPredicates('user');
predicates.orderByDesc('id');
const result = await this.store.query(predicates, ['id', 'name', 'age']);
const list: any[] = [];
while (result.goToNextRow()) {
list.push({
id: result.getLong(result.getColumnIndex('id')),
name: result.getString(result.getColumnIndex('name')),
age: result.getLong(result.getColumnIndex('age'))
});
}
this.users = list;
result.close();
}
build() {
Column() {
Button('➕ 添加用户').onClick(() => { this.addUser('用户' + Date.now(), 20); })
List() {
ForEach(this.users, (u: any) => {
ListItem() {
Text(`${u.name} --- ${u.age}岁`).padding(12)
}
}, (u: any) => u.id.toString())
}.layoutWeight(1).width('100%')
}.width('100%').height('100%').padding(16)
}
}
🎯 方案 4:fileIo 文件存储 --- 存图片/日志/缓存
typescript
import fileIo from '@ohos.file.fs';
async function saveToFile(context: any, filename: string, content: string) {
const path = context.filesDir + '/' + filename;
const file = await fileIo.open(path, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
await fileIo.write(file.fd, content);
fileIo.close(file);
}
async function readFromFile(context: any, filename: string): Promise<string> {
const path = context.filesDir + '/' + filename;
const file = await fileIo.open(path, fileIo.OpenMode.READ_ONLY);
const buf = new ArrayBuffer(4096);
const readLen = await fileIo.read(file.fd, buf);
fileIo.close(file);
return String.fromCharCode(...new Uint8Array(buf.slice(0, readLen)));
}
🎯 方案 5:分布式数据库 --- 跨设备同步
typescript
import distributedKVStore from '@ohos.data.distributedKVStore';
async function setupDistributedDB(context: any) {
const kvManager = await distributedKVStore.createKVManager({
bundleName: 'com.example.app',
context: context
});
const kvStore = await kvManager.getKVStore('sync_store', {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true // 自动同步到其他设备
});
// 写入(自动同步到登录了同一账号的其他设备)
await kvStore.put('key_hello', 'Hello from Device A');
// 读取
const value = await kvStore.get('key_hello');
}
📊 方案对比决策树
数据需要持久化?
├── 数据量极小(<10个键)?
│ └── ✅ PersistentStorage(@StorageLink 零代码)
├── 数据是 KV 键值对?
│ └── ✅ preferences(手动读写,灵活可控)
├── 数据有结构化关系?
│ └── ✅ SQLite relationalStore(支持 SQL 查询)
├── 数据是文件/二进制?
│ └── ✅ fileIo(图片、日志、导出)
└── 需要跨设备同步?
└── ✅ 分布式数据库(端云协同)
⚠️ 避坑指南
| 坑 | 原因 | 正确做法 |
|---|---|---|
| PersistentStorage 数据丢失 | 只支持基本类型,不支持对象 | 存 JSON.stringify + JSON.parse |
| preferences 写入不生效 | 忘了调用 flush() |
每次修改后调用 flush() 立即刷盘 |
| SQLite 打开失败 | 数据库文件损坏 | 用事务包裹写入操作 |
| fileIo 路径不对 | 用了硬编码路径 | 用 context.filesDir / cacheDir |
| 分布式数据不同步 | 没登录华为账号 | 登录同一华为账号 + 打开 WiFi/蓝牙 |
🔥 最佳实践
- 选型策略 :能用
PersistentStorage就不上preferences,能用preferences就不上 SQLite - JSON 序列化 :复杂对象用
JSON.stringify/parse转成字符串存储 - 异常处理 :所有存储操作都用
try/catch包裹 - 存储加密 :敏感数据(密码/Token)用
@ohos.security.huks加密后再存 - 定期清理 :
cacheDir中的缓存文件定期清理,避免撑爆存储 - 异步操作 :存储 API 都是异步的,用
async/await处理
官方文档: HarmonyOS 应用开发文档
- 开发者社区: 华为开发者论坛
- 欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net/
