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/

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐