HarmonyOS NEXT 数据持久化三剑客:Preferences、RelationalStore 与 KVDB 选型实战
HarmonyOS NEXT 数据持久化三剑客
- [HarmonyOS NEXT 数据持久化三剑客:Preferences、RelationalStore 与 KVDB 选型实战](#HarmonyOS NEXT 数据持久化三剑客:Preferences、RelationalStore 与 KVDB 选型实战)
-
- 前言
- 一、选型速查表
- 二、Preferences:轻量级配置存储
-
- [2.1 初始化与读写](#2.1 初始化与读写)
- [2.2 监听数据变化](#2.2 监听数据变化)
- [2.3 注意事项](#2.3 注意事项)
- 三、RelationalStore:关系型数据库
-
- [3.1 数据库初始化](#3.1 数据库初始化)
- [3.2 数据模型](#3.2 数据模型)
- [3.3 CRUD 操作](#3.3 CRUD 操作)
- [3.4 事务处理](#3.4 事务处理)
- 四、KVDB:分布式键值数据库
-
- [4.1 初始化](#4.1 初始化)
- [4.2 读写与跨设备监听](#4.2 读写与跨设备监听)
- [五、整合 ViewModel](#五、整合 ViewModel)
- [六、页面 UI 实现](#六、页面 UI 实现)
- 七、权限配置
- 八、性能对比
- 九、常见问题
- 总结
前言
在移动应用开发中,数据持久化是核心基础能力。HarmonyOS NEXT 提供了三种截然不同的本地存储方案:
- Preferences(用户首选项):轻量级 KV 存储,适合配置项
- RelationalStore(关系型数据库):SQLite 封装,适合结构化数据
- KVDB(分布式键值数据库):支持设备间同步,适合跨设备场景
本文通过完整的 ArkTS 代码示例,深入对比三种方案的适用场景、API 设计与性能特征,帮助你做出正确的技术选型。
适合读者:具备 HarmonyOS 基础开发经验,了解 ArkTS 语法的开发者。
一、选型速查表
| 维度 | Preferences | RelationalStore | KVDB |
|---|---|---|---|
| 数据结构 | Key-Value | 关系型表 | Key-Value |
| 数据量 | < 1 MB | 无限制 | 无限制 |
| 查询能力 | 仅按 Key | SQL 全量支持 | 仅按 Key/Range |
| 跨设备同步 | 不支持 | 不支持 | 支持(分布式) |
| 加密支持 | 不支持 | 支持 | 支持 |
| 典型场景 | 用户偏好、开关设置 | 订单、消息、联系人 | 跨设备剪贴板、协同状态 |
二、Preferences:轻量级配置存储
2.1 初始化与读写
Preferences 基于文件存储,每个实例对应一个本地文件,首次读写会将文件全量加载进内存。
typescript
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
const PREF_NAME = 'user_settings';
@ObservedV2
class SettingsViewModel {
@Trace isDarkMode: boolean = false;
@Trace fontSize: number = 16;
@Trace language: string = 'zh-CN';
private pref: preferences.Preferences | null = null;
async init(context: common.UIAbilityContext): Promise<void> {
this.pref = await preferences.getPreferences(context, PREF_NAME);
this.isDarkMode = (await this.pref.get('isDarkMode', false)) as boolean;
this.fontSize = (await this.pref.get('fontSize', 16)) as number;
this.language = (await this.pref.get('language', 'zh-CN')) as string;
}
async saveDarkMode(enabled: boolean): Promise<void> {
if (!this.pref) {
return;
}
this.isDarkMode = enabled;
await this.pref.put('isDarkMode', enabled);
await this.pref.flush();
}
async saveFontSize(size: number): Promise<void> {
if (!this.pref) {
return;
}
this.fontSize = size;
await this.pref.put('fontSize', size);
await this.pref.flush();
}
}
2.2 监听数据变化
Preferences 支持注册变更监听,实现跨组件配置同步:
typescript
async registerChangeListener(): Promise<void> {
if (!this.pref) {
return;
}
this.pref.on('change', (key: string) => {
if (key === 'isDarkMode') {
this.pref!.get('isDarkMode', false).then((val) => {
this.isDarkMode = val as boolean;
});
}
});
}
2.3 注意事项
flush()是异步持久化,应用退出前必须调用,否则数据只存在于内存中- 单个 Preferences 文件建议不超过 1 MB,过大会影响首次加载性能
- 不支持多进程并发写入,多进程场景应改用 KVDB
三、RelationalStore:关系型数据库
3.1 数据库初始化
RelationalStore 封装了 SQLite,支持完整的 SQL 操作与事务管理。
typescript
import { relationalStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
const DB_CONFIG: relationalStore.StoreConfig = {
name: 'app_database.db',
securityLevel: relationalStore.SecurityLevel.S1,
};
class DatabaseManager {
private store: relationalStore.RdbStore | null = null;
async init(context: common.UIAbilityContext): Promise<void> {
this.store = await relationalStore.getRdbStore(context, DB_CONFIG);
const createSql =
'CREATE TABLE IF NOT EXISTS tasks (' +
'id INTEGER PRIMARY KEY AUTOINCREMENT,' +
'title TEXT NOT NULL,' +
'isDone INTEGER DEFAULT 0,' +
'priority INTEGER DEFAULT 1,' +
'createdAt INTEGER NOT NULL)';
await this.store.executeSql(createSql);
}
getStore(): relationalStore.RdbStore {
if (!this.store) {
throw new Error('Database not initialized');
}
return this.store;
}
}
const dbManager = new DatabaseManager();
3.2 数据模型
typescript
class Task {
id: number = 0;
title: string = '';
isDone: boolean = false;
priority: number = 1;
createdAt: number = 0;
}
3.3 CRUD 操作
typescript
class TaskRepository {
async insert(task: Task): Promise<number> {
const values: relationalStore.ValuesBucket = {
title: task.title,
isDone: task.isDone ? 1 : 0,
priority: task.priority,
createdAt: task.createdAt,
};
return await dbManager.getStore().insert('tasks', values);
}
async queryAll(): Promise<Task[]> {
const predicates = new relationalStore.RdbPredicates('tasks');
predicates.orderByDesc('createdAt');
const cursor = await dbManager.getStore().query(
predicates,
['id', 'title', 'isDone', 'priority', 'createdAt']
);
const tasks: Task[] = [];
while (cursor.goToNextRow()) {
const t = new Task();
t.id = cursor.getLong(cursor.getColumnIndex('id'));
t.title = cursor.getString(cursor.getColumnIndex('title'));
t.isDone = cursor.getLong(cursor.getColumnIndex('isDone')) === 1;
t.priority = cursor.getLong(cursor.getColumnIndex('priority'));
t.createdAt = cursor.getLong(cursor.getColumnIndex('createdAt'));
tasks.push(t);
}
cursor.close();
return tasks;
}
async update(id: number, isDone: boolean): Promise<number> {
const values: relationalStore.ValuesBucket = { isDone: isDone ? 1 : 0 };
const predicates = new relationalStore.RdbPredicates('tasks');
predicates.equalTo('id', id);
return await dbManager.getStore().update(values, predicates);
}
async delete(id: number): Promise<number> {
const predicates = new relationalStore.RdbPredicates('tasks');
predicates.equalTo('id', id);
return await dbManager.getStore().delete(predicates);
}
}
3.4 事务处理
批量写入务必使用事务,性能差距可达 200 倍:
typescript
async batchInsert(tasks: Task[]): Promise<void> {
const store = dbManager.getStore();
await store.beginTransaction();
try {
for (let i = 0; i < tasks.length; i++) {
const t = tasks[i];
const values: relationalStore.ValuesBucket = {
title: t.title,
isDone: t.isDone ? 1 : 0,
priority: t.priority,
createdAt: t.createdAt,
};
await store.insert('tasks', values);
}
store.commit();
} catch (e) {
store.rollBack();
throw e;
}
}
四、KVDB:分布式键值数据库
4.1 初始化
KVDB 支持设备间数据自动同步,适合需要跨设备共享状态的场景。
typescript
import { distributedKVStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
class KVStoreManager {
private kvManager: distributedKVStore.KVManager | null = null;
private kvStore: distributedKVStore.SingleKVStore | null = null;
async init(context: common.UIAbilityContext): Promise<void> {
const config: distributedKVStore.KVManagerConfig = {
context: context,
bundleName: 'com.example.myapp',
};
this.kvManager = distributedKVStore.createKVManager(config);
const options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S1,
};
this.kvStore = await this.kvManager.getKVStore<distributedKVStore.SingleKVStore>(
'shared_store',
options
);
}
getStore(): distributedKVStore.SingleKVStore {
if (!this.kvStore) {
throw new Error('KVStore not initialized');
}
return this.kvStore;
}
}
const kvStoreManager = new KVStoreManager();
4.2 读写与跨设备监听
typescript
class SyncRepository {
async save(key: string, value: string): Promise<void> {
await kvStoreManager.getStore().put(key, value);
}
async get(key: string): Promise<string> {
const entry = await kvStoreManager.getStore().get(key);
return entry as string;
}
subscribeRemoteChanges(callback: (key: string) => void): void {
kvStoreManager.getStore().on(
'dataChange',
distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE,
(data) => {
const updated = data.updateEntries;
for (let i = 0; i < updated.length; i++) {
callback(updated[i].key);
}
}
);
}
}
五、整合 ViewModel
将三种存储方案整合进 MVVM 架构:
typescript
@ObservedV2
class AppDataViewModel {
@Trace tasks: Task[] = [];
@Trace isDarkMode: boolean = false;
@Trace isLoading: boolean = false;
@Trace errorMsg: string = '';
private settingsVm: SettingsViewModel = new SettingsViewModel();
private taskRepo: TaskRepository = new TaskRepository();
async initialize(context: common.UIAbilityContext): Promise<void> {
this.isLoading = true;
try {
await dbManager.init(context);
await this.settingsVm.init(context);
this.isDarkMode = this.settingsVm.isDarkMode;
this.tasks = await this.taskRepo.queryAll();
} catch (e) {
this.errorMsg = '初始化失败';
} finally {
this.isLoading = false;
}
}
async addTask(title: string): Promise<void> {
const t = new Task();
t.title = title;
t.isDone = false;
t.priority = 1;
t.createdAt = Date.now();
await this.taskRepo.insert(t);
this.tasks = await this.taskRepo.queryAll();
}
async toggleTask(id: number, isDone: boolean): Promise<void> {
await this.taskRepo.update(id, isDone);
this.tasks = await this.taskRepo.queryAll();
}
async toggleDarkMode(): Promise<void> {
await this.settingsVm.saveDarkMode(!this.isDarkMode);
this.isDarkMode = this.settingsVm.isDarkMode;
}
}
六、页面 UI 实现
typescript
@Entry
@ComponentV2
struct DataPersistencePage {
@Local private vm: AppDataViewModel = new AppDataViewModel();
private context = getContext(this) as common.UIAbilityContext;
aboutToAppear(): void {
this.vm.initialize(this.context);
}
@Builder
private renderTaskItem(task: Task): void {
Row() {
Checkbox()
.select(task.isDone)
.onChange((val: boolean) => {
this.vm.toggleTask(task.id, val);
})
Text(task.title)
.fontSize(16)
.fontColor(task.isDone ? Color.Gray : Color.Black)
.decoration({
type: task.isDone ? TextDecorationType.LineThrough : TextDecorationType.None
})
.layoutWeight(1)
.margin({ left: 12 })
}
.width('100%')
.padding(12)
.backgroundColor(Color.White)
.borderRadius(8)
}
build() {
Column({ space: 12 }) {
Row() {
Text('任务管理').fontSize(20).fontWeight(FontWeight.Bold)
Toggle({ type: ToggleType.Switch, isOn: this.vm.isDarkMode })
.onChange(() => { this.vm.toggleDarkMode(); })
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({ left: 16, right: 16, top: 16 })
if (this.vm.isLoading) {
LoadingProgress().width(48).height(48)
} else {
List({ space: 8 }) {
ForEach(this.vm.tasks, (task: Task) => {
ListItem() { this.renderTaskItem(task) }
})
}
.padding({ left: 16, right: 16 })
.layoutWeight(1)
}
Button('添加任务')
.width('90%')
.onClick(() => { this.vm.addTask('新任务 ' + Date.now()); })
.margin({ bottom: 24 })
}
.width('100%')
.height('100%')
.backgroundColor(this.vm.isDarkMode ? '#1a1a1a' : '#f5f5f5')
}
}
七、权限配置
使用 KVDB 分布式同步功能需要在 module.json5 中声明:
json
{
"requestPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC",
"reason": "$string:distributed_sync_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
八、性能对比
| 方案 | 单次写入延迟 | 批量写入(1000 条) | 适合数据量 |
|---|---|---|---|
| Preferences | < 1 ms(内存) | 不适合批量 | < 1 MB |
| RelationalStore(有事务) | ~2 ms/条 | ~50 ms | 无限制 |
| RelationalStore(无事务) | ~10 ms/条 | ~10 s | --- |
| KVDB | ~5 ms/条 | ~500 ms | 无限制 |
核心结论:RelationalStore 批量写入务必使用事务,性能差距可达 200 倍。
九、常见问题
Q1:Preferences.flush() 之后数据还是丢失?
检查是否在多个进程中同时操作同一个 Preferences 文件。多进程场景下仅 KVDB 能保证一致性。
Q2:RelationalStore 查询返回空结果?
- 确认
executeSql()建表 SQL 已正确执行(IF NOT EXISTS不报错,需查日志) - 检查
RdbPredicates的列名拼写是否与建表 SQL 完全一致
Q3:KVDB 跨设备同步未触发?
- 两台设备已登录同一华为账号且处于同一局域网
- 确认 Options 中
autoSync: true已设置 - 检查运行时是否已动态申请
ohos.permission.DISTRIBUTED_DATASYNC权限
总结
| 场景 | 推荐方案 |
|---|---|
| 用户偏好、开关、主题色 | Preferences |
| 聊天记录、订单、联系人 | RelationalStore |
| 跨设备剪贴板、协同编辑状态 | KVDB |
| 本地缓存 + 分页加载 | RelationalStore |
| 轻量级会话 Token | Preferences |
掌握三种存储方案的核心差异,根据数据量 、查询复杂度 、跨设备需求三个维度做出选型,是构建高质量 HarmonyOS 应用的基础能力。在实际项目中,三者往往组合使用:Preferences 管理轻量配置,RelationalStore 持久化结构化数据,KVDB 在需要时打通设备边界。
参考资料