【鸿蒙】HarmonyOS 数据持久化:Preferences/KV Store/RelationalStore 选型指南
1.配置项 → Preferences,全量内存操作快,但不适合多进程和大数据。2.跨设备共享 → KV Store,autoSync 方便但要控制频率防止流量爆炸。3.结构化业务数据 → RelationalStore,必须用索引、事务、子线程三件套。4.DB 操作永远不要在主线程,TaskPool 是最简单的解法。5.游标用完必须 close(),这是 SQLite 连接泄漏最常见的来源。核心
HarmonyOS 数据持久化:Preferences/KV Store/RelationalStore 选型指南
> 读完本文,你将掌握三种持久化 API 的核心差异、选型决策树,以及常见坑点的规避方法,不再因选错存储方案而踩坑。
> 适用版本:HarmonyOS NEXT / API 12+ | ⏱ 阅读约 18 分钟
---
背景:你真的需要数据库吗?
很多开发者在需要"保存一个布尔值"时直接引入关系型数据库,又在需要"存储用户行为日志"时用 Preferences。选型错误带来的不只是性能损耗,还有数据一致性风险。
HarmonyOS NEXT 提供三条数据持久化路径:
应用数据持久化 API
├── Preferences(用户首选项)
│ └── 轻量 KV,XML 序列化,适合配置项
├── KV Store(分布式键值存储)
│ └── 跨设备同步,适合轻量共享状态
└── RelationalStore(关系型数据库)
└── SQLite 封装,适合结构化/大量数据
---
一、Preferences:用户配置的最优解
1.1 内部机制
Preferences 将数据序列化为 XML 文件存储在应用沙箱,整个文件在首次访问时全量加载进内存,后续读写操作均在内存中完成,调用 flush() 才落盘。
Preferences 读写流程:
┌──────────┐ getPreferences() ┌──────────────┐
│ Context │ ──────────────────▶ │ 内存 Map│
└──────────┘ └──────┬───────┘
│ flush() / 进程退出
┌─────▼──────┐
│ .xml 文件 │
└────────────┘
1.2 基本用法
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
// ✅ 正确:在 UIAbility 中获取 context 后使用
const PREF_NAME = 'user_settings';
async function saveTheme(context: common.UIAbilityContext, isDark: boolean) {
// 获取 Preferences 实例(同名文件只会加载一次)
const pref = await preferences.getPreferencesSync(context, { name: PREF_NAME });
pref.putSync('isDarkMode', isDark); // 写入内存
await pref.flush(); // 必须 flush 才落盘!
}
async function readTheme(context: common.UIAbilityContext): Promise {
const pref = await preferences.getPreferencesSync(context, { name: PREF_NAME });
return pref.getSync('isDarkMode', false) as boolean; // 第二参数为默认值
}
错误写法 → 问题 → 正确写法
| 错误写法 | 问题 | 正确写法 |
|---------|------|---------|
| pref.put('key', val) 后不调用 flush() | 应用闪退时数据丢失 | 每次写入后调用 await pref.flush() |
| 在 @Entry 组件 build() 中调用 getPreferences() | 每次渲染都重建实例,内存浪费 | 在 aboutToAppear() 或 Service 层中统一初始化 |
| 用 Preferences 存储超过 8KB 的字符串 | XML 解析变慢,全量加载内存压力大 | 超过 8KB 的数据改用 RelationalStore 或 KV Store |
1.3 适用场景
- 用户偏好设置(主题、语言、字体大小)
- 开关类配置(通知开启状态、是否首次启动)
- 轻量数值(上次浏览位置、计数器)
不适用: 超过 100 个 key、单 value 超 8KB、需要多线程并发写入。---
二、KV Store:分布式场景的首选
2.1 架构概览
KV Store 基于 HarmonyOS 分布式数据服务(DDS),本地写入会通过可信通道同步到同账号下的其他设备。
KV Store 数据流:
手机 App 平板 App
┌──────────┐ 同步通道 ┌──────────┐
│ KVManager│ ◀──────────▶ │ KVManager│
│ Local DB│ │ Local DB│
└────┬─────┘ └──────────┘
│ 本地持久化
┌──▼──────┐
│RocksDB │ ← 底层存储引擎
└─────────┘
2.2 初始化与读写
import { distributedKVStore } from '@kit.ArkData';
class KVStoreManager {
private kvManager: distributedKVStore.KVManager | null = null;
private kvStore: distributedKVStore.SingleKVStore | null = null;
async init(context: Context) {
const config: distributedKVStore.KVManagerConfig = {
context,
bundleName: 'com.example.myapp',
};
this.kvManager = distributedKVStore.createKVManager(config);
// SINGLE_VERSION:单设备版本,不冲突;DEVICE_COLLABORATION:多设备协同
const options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true, // 自动同步到同账号设备
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
schema: undefined,
securityLevel: distributedKVStore.SecurityLevel.S1,
};
this.kvStore = await this.kvManager.getKVStore(
'my_store_id', options
);
}
async put(key: string, value: string) {
await this.kvStore!.put(key, value);
}
async get(key: string): Promise {
const data = await this.kvStore!.get(key);
return data as string;
}
}
错误写法 → 问题 → 正确写法
| 错误写法 | 问题 | 正确写法 |
|---------|------|---------|
| 每次操作都调用 getKVStore() | 频繁创建销毁,性能差 | 单例初始化,App 生命周期内复用 |
| autoSync: true + 高频写入 | 流量消耗大,影响电量 | 高频场景设 autoSync: false,批量完成后手动 sync() |
| 不监听 DATA_CHANGE 事件 | 跨设备更新后 UI 不刷新 | 注册 on('dataChange', ...) 回调更新状态 |
---
三、RelationalStore:结构化数据的唯一选择
3.1 核心能力
RelationalStore 是对 SQLite 的封装,支持事务、索引、复杂 WHERE 查询,提供 ValuesBucket/RdbPredicates 等 ArkTS 友好 API。
RelationalStore 调用链:
┌──────────────┐
│ RdbPredicates│ ← 类型安全的条件构建器
└──────┬───────┘
│ equalTo / between / orderByDesc ...
┌──────▼───────┐ SQL 翻译 ┌────────┐
│ RdbStore │ ────────────▶│ SQLite │
└──────┬───────┘ └────────┘
│ ResultSet
┌──────▼───────┐
│ 数据游标 │ ← 惰性加载,不一次性拷贝全部数据
└──────────────┘
3.2 完整 CRUD 示例
import { relationalStore } from '@kit.ArkData';
const DB_CONFIG: relationalStore.StoreConfig = {
name: 'notes.db',
securityLevel: relationalStore.SecurityLevel.S1,
};
const CREATE_TABLE = `
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT,
ts INTEGER DEFAULT (strftime('%s','now'))
)`;
let rdbStore: relationalStore.RdbStore | null = null;
async function initDB(context: Context) {
rdbStore = await relationalStore.getRdbStore(context, DB_CONFIG);
await rdbStore.executeSql(CREATE_TABLE);
}
async function insertNote(title: string, body: string): Promise {
const bucket: relationalStore.ValuesBucket = { title, body };
return await rdbStore!.insert('notes', bucket);
}
async function queryNotes(keyword: string) {
const predicates = new relationalStore.RdbPredicates('notes');
predicates.contains('title', keyword)
.orderByDesc('ts')
.limitAs(20);
const cursor = await rdbStore!.query(predicates, ['id', 'title', 'body', 'ts']);
const results: Array<{ id: number; title: string }> = [];
while (cursor.goToNextRow()) {
results.push({
id: cursor.getLong(cursor.getColumnIndex('id')),
title: cursor.getString(cursor.getColumnIndex('title')),
});
}
cursor.close(); // ⚠️ 必须手动 close,否则 SQLite 连接泄漏
return results;
}
async function batchInsert(notes: Array<{ title: string; body: string }>) {
await rdbStore!.beginTransaction();
try {
for (const n of notes) {
await rdbStore!.insert('notes', { title: n.title, body: n.body });
}
await rdbStore!.commit();
} catch (e) {
await rdbStore!.rollBack();
throw e;
}
}
错误写法 → 问题 → 正确写法
| 错误写法 | 问题 | 正确写法 |
|---------|------|---------|
| 每次读写都 getRdbStore() | SQLite 连接建立代价大,频繁开关严重拖慢速度 | AbilityStage 中单例初始化,全局复用 |
| cursor.query() 后不 cursor.close() | 连接未释放,并发查询超出上限后 crash | 使用 try-finally 保证 cursor.close() 执行 |
| 不使用事务批量插入 1000 条 | 每条独立事务,速度降低约 50 倍 | 用 beginTransaction/commit 包裹批量操作 |
| 手写 SQL 字符串拼接用户输入 | SQL 注入风险 | 始终使用 RdbPredicates 构建条件 |
---
四、选型决策树
需要持久化数据?
├── 是跨设备同步数据?
│ └── YES → KV Store(autoSync: true)
│ └── NO ↓
├── 数据量 < 100 条 且 结构简单(KV 对)?
│ └── YES → Preferences
│ └── NO ↓
└── 需要结构化查询 / 数据量大 / 需要事务?
└── YES → RelationalStore(SQLite)
| 维度 | Preferences | KV Store | RelationalStore |
|------|-------------|----------|-----------------|
| 数据结构 | 扁平 KV | 扁平 KV | 关系型表 |
| 存储引擎 | XML 文件 | RocksDB | SQLite |
| 跨设备同步 | 不支持 | 原生支持 | 不支持 |
| 事务支持 | 不支持 | 不支持 | 支持 |
| 最大数据量 | < 100 个 key | 中等 | 理论无上限 |
| 查询能力 | 按 key 精确 | 按 key 精确 | SQL 全功能 |
---
五、最佳实践
5.1 统一在 AbilityStage 中初始化存储
做法: 在AbilityStage.onCreate() 中初始化所有持久化实例,注入到 AppStorage。 原因: AbilityStage 早于 UIAbility 启动,确保 UI 层使用时实例已就绪,避免空指针异常。 对比: 若在 @Entry 组件的 aboutToAppear() 中初始化,多个页面会重复创建实例,且页面切换时可能出现短暂不可用窗口。
5.2 Preferences 的 flush 策略
做法: 普通配置项写入后调用flush();高频临时状态在 UIAbility.onBackground() 中统一落盘。 原因: flush() 是磁盘 I/O,频繁调用导致 UI 卡顿或过多 I/O 唤醒耗电。 对比: 每次写都 flush() 会使 60fps 滚动掉帧;完全不 flush() 则进程被杀后丢数据。
5.3 RelationalStore 必须使用索引
做法: 对 WHERE/ORDER BY 字段建索引:CREATE INDEX IF NOT EXISTS idx_ts ON notes(ts)。 原因: 无索引的全表扫描在 10 万行时耗时可达数百毫秒,阻塞 UI 线程直接导致 ANR。 对比: 加索引后相同查询降至个位数毫秒;但不必要的索引会拖慢写入,按需建立。
5.4 KV Store 的变更监听
做法: 注册kvStore.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE, callback) 监听跨设备数据变更。 原因: 不注册监听时跨设备同步数据只在本地 RocksDB 中更新,UI 层无感知,用户看到"没同步"假象。 对比: 注册后跨设备延迟通常在 200ms 以内,用户几乎无感。
---
六、常见坑点
坑 1:Preferences 多进程写入数据丢失
- 现象: 后台 Service 和前台 UI 同时写 Preferences,偶发数据覆盖或丢失。
- 原因: Preferences 在每个进程中独立加载 XML 到内存,进程 A flush 后,进程 B 的内存镜像无感知,B flush 时覆盖 A 的数据。
- 复现: 在 ServiceExtensionAbility 和 UIAbility 中同时写同一个 Preferences 文件。
- 解决: 多进程场景改用 KV Store,或通过 IPC 集中到单进程写入。
坑 2:RelationalStore 主线程查询导致 ANR
- 现象: 列表滚动时偶发页面冻结 2~5 秒。
- 原因: rdbStore.query() 是耗时 I/O,在主线程调用时阻塞 UI 渲染管道。
- 复现: 在 aboutToAppear 中直接 await rdbStore.query(...),数据量 > 1 万行时稳定复现。
- 解决: 所有 DB 操作移至 TaskPool 或 Worker 线程,通过 @State 回调更新 UI。
import { taskpool } from '@kit.ArkTS';
@Concurrent
async function queryTask(keyword: string): Promise {
return await queryNotes(keyword).then(r => r.map(n => n.title));
}
taskpool.execute(queryTask, this.keyword).then((titles: string[]) => {
this.list = titles;
});
坑 3:KV Store autoSync 导致流量异常
- 现象: 应用后台流量消耗异常,单日 > 50MB。
- 原因: autoSync: true 时每次 put() 都触发同步,高频写入导致同步风暴。
- 复现: 循环中每 100ms 执行一次 kvStore.put(),双设备环境下流量监控立即飙升。
- 解决: 高频写场景设 autoSync: false,积攒一批后手动调用 kvStore.sync()。
---
七、总结
1. 配置项 → Preferences,全量内存操作快,但不适合多进程和大数据。
2. 跨设备共享 → KV Store,autoSync 方便但要控制频率防止流量爆炸。
3. 结构化业务数据 → RelationalStore,必须用索引、事务、子线程三件套。
4. DB 操作永远不要在主线程,TaskPool 是最简单的解法。
5. 游标用完必须 close(),这是 SQLite 连接泄漏最常见的来源。
> 核心结论:先问数据量和查询复杂度,再选存储方案,而不是先选方案再填需求。
---
参考资料
- 官方文档 - 用户首选项(Preferences):https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/data-persistence-by-preferences-V5
- 官方文档 - 键值型数据库(KV Store):https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/data-persistence-by-kv-store-V5
- 官方文档 - 关系型数据库(RelationalStore):https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/data-persistence-by-rdb-store-V5
- OpenHarmony 源码 - Preferences 实现:foundation/distributeddatamgr/preferences/frameworks/
- OpenHarmony 源码 - KV Store:foundation/distributeddatamgr/kv_store/frameworks/
- OpenHarmony 源码 - RelationalStore:foundation/distributeddatamgr/relational_store/frameworks/
更多推荐

所有评论(0)