OpenHarmony 数据持久化实践
·
一、前言 & 背景
开发 OpenHarmony 应用时,数据存内存里会碰到三个典型痛点:
- 进程被杀数据就没了:用户配置的 WiFi 名称、设备参数、开关状态,App 一退出全部丢失,每次都要重新配置;
- 跨页面/跨启动无法共享:A 页面保存的配置,B 页面和下次启动都拿不到;
- 查询效率低:设备列表、历史记录这类结构化数据,如果每次启动都重新扫描/重新组装,体验很差。
OpenHarmony 提供了完整的持久化能力:轻量级键值对 Preferences、关系型数据库 RDB、键值型数据库 KV-Store。本文以"用户配置 + 设备信息管理"为线索,讲清楚三种方案怎么选、怎么写、怎么避坑。
二、哪些场景适合用?
| 存储方案 | 适合场景 | 特点 |
|---|---|---|
| Preferences | 用户偏好配置、开关状态、少量键值数据(如配置 JSON 串、测试标记位) | 轻量、读写快、按 key 存取,不适合大量/复杂查询 |
| RDB(关系型数据库) | 结构化数据:设备信息表、授权码表、历史记录,需要条件查询/排序/关联 | 基于 SQLite,支持 SQL、事务、索引 |
| KV-Store(键值型数据库) | 无复杂关系模型的键值数据(商品价格、出勤状态、单设备缓存),尤其适合多设备协同同步场景 | 单版本/设备协同两种类型,支持分布式同步与数据变更监听 |
选型原则一句话:配置类数据用 Preferences,表结构数据用 RDB,需要分布式同步的键值数据用 KV-Store。
三、核心机制
3.1 Preferences
- 以"Preferences 实例"为单位组织数据,实例绑定应用沙箱目录下的一个文件;
- 数据以键值对(key-value)形式存储,支持 string、number、boolean、Object(需 JSON 序列化);
- 修改后必须调用 flush() 落盘,否则仅存在内存中;
- 实例可通过 getPreferences 重复获取,操作的是同一份数据。
3.2 RDB
- 通过 getRdbStore 创建/打开数据库,表结构用 executeSql 建表;
- 增删改查走 insert / update / delete / query,条件用 RdbPredicates 构造;
- 查询结果返回 ResultSet,遍历后必须 close() 释放;
- 支持事务(beginTransaction / commit / rollBack)保证多表操作一致性。
3.3 KV-Store
- 通过 createKVManager 创建数据库管理器,再 getKVStore(storeId, options) 获取数据库实例;
- 有两种类型:单版本数据库(SINGLE_VERSION,仅本设备)与设备协同数据库(DEVICE_COLLABORATION,支持多设备同步);
- 数据以键值对存储,put 已存在的 key 时更新、不存在时新增;
- 支持 on('dataChange') 监听数据变更(本机修改与远端同步都会触发);
- 设备协同场景需要 ohos.permission.DISTRIBUTED_DATASYNC 权限,跨设备同步依赖组网;
- 约束:单版本数据库 Key ≤ 1KB、Value < 4MB;设备协同数据库 Key ≤ 896B、Value < 4MB;单应用最多同时打开 16 个键值数据库。
四、核心 API
4.1 Preferences
| 接口 | 说明 |
|---|---|
| data_preferences.getPreferences(context, name) | 获取 Preferences 实例(不存在则创建) |
| preferences.put(key, value) | 写入键值对(内存) |
| preferences.get(key, defaultValue) | 读取键值 |
| preferences.delete(key) | 删除键值 |
| preferences.flush() | 将内存修改异步落盘 |
4.2 RDB
| 接口 | 说明 |
|---|---|
| relationalStore.getRdbStore(context, config) | 创建/打开数据库(config 含 name、securityLevel) |
| rdbStore.executeSql(sql) | 执行建表等 SQL |
| rdbStore.insert(table, valuesBucket) | 插入一行 |
| rdbStore.query(table, predicates) | 条件查询,返回 ResultSet |
| rdbStore.update / delete | 更新/删除 |
| rdbStore.beginTransaction / commit / rollBack | 事务控制 |
4.3 KV-Store
| 接口 | 说明 |
|---|---|
| distributedKVStore.createKVManager(config) | 创建 KVManager(config 含 bundleName、context) |
| kvManager.getKVStore(storeId, options) | 创建/获取 KVStore(options 含 kvStoreType、encrypt、autoSync、securityLevel) |
| kvStore.put(key, value) | 写入键值对(key 存在则更新) |
| kvStore.get(key) | 读取键值 |
| kvStore.delete(key) | 删除键值 |
| kvStore.on('dataChange', type, callback) / off | 订阅/取消数据变更 |
| kvManager.closeKVStore / deleteKVStore | 关闭/删除数据库 |
五、示例
5.1 场景说明
- 用 Preferences 保存用户配置(如 WiFi 名称、开关状态),退出重进可恢复;
- 用 RDB 维护"设备信息表",支持按设备 ID 增删改查。
- 用 KV-Store 维护"设备状态缓存",借助设备协同库实现多设备数据同步。
5.2 Preferences:保存与恢复配置
import data_preferences from '@ohos.data.preferences';
import { common } from '@kit.AbilityKit';
export class ConfigManager {
private static readonly STORE_NAME = 'config_preference';
private static readonly KEY_CONFIG = 'cfg_full';
/** 保存配置:对象序列化为 JSON 后写入 */
public static async saveConfig(context: common.Context, config: Record<string, Object>): Promise<void> {
let preferences = await data_preferences.getPreferences(context, ConfigManager.STORE_NAME);
await preferences.put(ConfigManager.KEY_CONFIG, JSON.stringify(config));
await preferences.flush(); // 关键:flush 后数据才真正落盘
}
/** 恢复配置:不存在返回 null */
public static async loadConfig(context: common.Context): Promise<Record<string, Object> | null> {
let preferences = await data_preferences.getPreferences(context, ConfigManager.STORE_NAME);
let saved = await preferences.get(ConfigManager.KEY_CONFIG, '') as string;
if (!saved) {
return null;
}
return JSON.parse(saved) as Record<string, Object>;
}
/** 清除配置 */
public static async clearConfig(context: common.Context): Promise<void> {
let preferences = await data_preferences.getPreferences(context, ConfigManager.STORE_NAME);
await preferences.delete(ConfigManager.KEY_CONFIG);
await preferences.flush();
}
}
典型用法:进入配置页时 loadConfig 回填表单;点击"保存"时 saveConfig 写入,下次启动自动恢复。
5.3 RDB:设备信息表增删改查
import { relationalStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
export interface DeviceInfo {
deviceId: string;
deviceName: string;
mac: string;
connectType: number;
}
export class DeviceInfoDb {
private static rdbStore: relationalStore.RdbStore | null = null;
private static readonly TABLE_NAME = 'device_info';
/** 创建/打开数据库并建表 */
public static async init(context: common.Context): Promise<void> {
let config: relationalStore.StoreConfig = {
name: 'device.db',
securityLevel: relationalStore.SecurityLevel.S1
};
DeviceInfoDb.rdbStore = await relationalStore.getRdbStore(context, config);
await DeviceInfoDb.rdbStore.executeSql(
`CREATE TABLE IF NOT EXISTS ${DeviceInfoDb.TABLE_NAME} (
device_id TEXT PRIMARY KEY,
device_name TEXT,
mac TEXT,
connect_type INTEGER
)`);
}
/** 插入设备信息 */
public static async addDevice(device: DeviceInfo): Promise<number> {
if (!DeviceInfoDb.rdbStore) {
return -1;
}
let value: relationalStore.ValuesBucket = {
'device_id': device.deviceId,
'device_name': device.deviceName,
'mac': device.mac,
'connect_type': device.connectType
};
return await DeviceInfoDb.rdbStore.insert(DeviceInfoDb.TABLE_NAME, value);
}
/** 按设备 ID 查询 */
public static async queryDevice(deviceId: string): Promise<DeviceInfo | null> {
if (!DeviceInfoDb.rdbStore) {
return null;
}
let predicates = new relationalStore.RdbPredicates(DeviceInfoDb.TABLE_NAME);
predicates.equalTo('device_id', deviceId);
let resultSet = await DeviceInfoDb.rdbStore.query(predicates);
try {
if (resultSet.goToFirstRow()) {
return {
deviceId: resultSet.getString(resultSet.getColumnIndex('device_id')),
deviceName: resultSet.getString(resultSet.getColumnIndex('device_name')),
mac: resultSet.getString(resultSet.getColumnIndex('mac')),
connectType: resultSet.getLong(resultSet.getColumnIndex('connect_type'))
} as DeviceInfo;
}
return null;
} finally {
resultSet.close(); // 关键:ResultSet 必须释放
}
}
/** 更新设备名称 */
public static async updateDeviceName(deviceId: string, newName: string): Promise<number> {
if (!DeviceInfoDb.rdbStore) {
return 0;
}
let predicates = new relationalStore.RdbPredicates(DeviceInfoDb.TABLE_NAME);
predicates.equalTo('device_id', deviceId);
let value: relationalStore.ValuesBucket = { 'device_name': newName };
return await DeviceInfoDb.rdbStore.update(value, predicates);
}
/** 删除设备 */
public static async deleteDevice(deviceId: string): Promise<number> {
if (!DeviceInfoDb.rdbStore) {
return 0;
}
let predicates = new relationalStore.RdbPredicates(DeviceInfoDb.TABLE_NAME);
predicates.equalTo('device_id', deviceId);
return await DeviceInfoDb.rdbStore.delete(predicates);
}
}
5.4 KV-Store:设备状态缓存与数据变更监听
import { distributedKVStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
export class DeviceStateStore {
private static kvManager: distributedKVStore.KVManager | null = null;
private static kvStore: distributedKVStore.SingleKVStore | null = null;
private static readonly STORE_ID = 'device_state_store';
/** 创建 KVManager 并获取数据库实例 */
public static async init(context: common.Context): Promise<void> {
let kvManagerConfig: distributedKVStore.KVManagerConfig = {
bundleName: context.abilityInfo.name,
context: context
};
DeviceStateStore.kvManager = distributedKVStore.createKVManager(kvManagerConfig);
let options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true, // 开启自动同步(设备协同场景)
// 不填 kvStoreType 默认创建多设备协同数据库;本机使用可指定单版本:
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S1
};
DeviceStateStore.kvStore = await DeviceStateStore.kvManager.getKVStore(
DeviceStateStore.STORE_ID, options) as distributedKVStore.SingleKVStore;
}
/** 写入设备状态(key 已存在则更新) */
public static async putState(deviceId: string, state: string): Promise<void> {
if (!DeviceStateStore.kvStore) {
return;
}
try {
await DeviceStateStore.kvStore.put(deviceId, state);
} catch (err) {
let e = err as BusinessError;
console.error('put failed, code is ' + e.code + ', message is ' + e.message);
}
}
/** 读取设备状态 */
public static async getState(deviceId: string): Promise<string | undefined> {
if (!DeviceStateStore.kvStore) {
return undefined;
}
try {
return await DeviceStateStore.kvStore.get(deviceId) as string;
} catch (err) {
let e = err as BusinessError;
console.error('get failed, code is ' + e.code + ', message is ' + e.message);
return undefined;
}
}
/** 订阅数据变更:本机修改与远端同步都会触发回调 */
public static watchDataChange(onChange: (data: distributedKVStore.ChangeNotification) => void): void {
DeviceStateStore.kvStore?.on('dataChange',
distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, onChange);
}
/** 关闭并删除数据库(appId 为应用 bundleName) */
public static async close(): Promise<void> {
if (DeviceStateStore.kvManager) {
await DeviceStateStore.kvManager.closeKVStore('com.example.app', DeviceStateStore.STORE_ID);
}
}
}
5.5 内存缓存 + 持久化双层结构(工程实践)
实际工程中常采用"内存 Map 做热缓存,RDB 做持久化"的双层结构:读时优先查内存,写时内存与数据库同步更新,兼顾性能与可靠性。
export class DeviceInfoCache {
private static cacheMap = new Map<string, DeviceInfo>();
public static get(deviceId: string): DeviceInfo | undefined {
return DeviceInfoCache.cacheMap.get(deviceId);
}
public static async put(device: DeviceInfo): Promise<void> {
DeviceInfoCache.cacheMap.set(device.deviceId, device); // 先更新内存
await DeviceInfoDb.addDevice(device); // 再落库
}
public static async remove(deviceId: string): Promise<void> {
DeviceInfoCache.cacheMap.delete(deviceId);
await DeviceInfoDb.deleteDevice(deviceId);
}
/** 启动时从数据库恢复缓存 */
public static async warmUp(): Promise<void> {
// 全表查询并写入 cacheMap,避免频繁访问数据库
}
}
六、常见问题与踩坑总结
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 数据重启后丢失 | put 后未调用 flush() | Preferences 修改后必须 flush() 落盘 |
| 读到旧数据 | 实例缓存未刷新或未重新 get | 使用同一个 Preferences 实例;跨页面读取前重新 getPreferences |
| getPreferences 每次拿到的实例不一致 | 使用了不同 name | 全局统一 STORE_NAME 常量,一处修改全局生效 |
| RDB 查询崩溃/内存上涨 | ResultSet 未 close | 查询结果用完在 finally 中 close() |
| 重复插入报主键冲突 | 设备 ID 主键已存在 | 插入前先按主键查询,存在则走 update |
| 并发写导致数据错乱 | 多页面同时写库 | 使用事务包裹批量操作;配置类写入统一走单例管理器 |
| 数据库升级失败 | 表结构变更无迁移逻辑 | 使用 StoreConfig 版本号 + onUpgrade 做增量迁移 |
| 大对象直接存 Preferences 失败 | 超过单 key 大小限制 | 压缩或拆分存储;结构化大对象建议入 RDB |
| KV-Store 写入超大 Value 失败 | 超过单条记录大小限制(单版本 Value < 4MB) | 拆分存储或改用 RDB;注意 Key 长度限制(≤ 1KB) |
| KV-Store 事件回调阻塞卡顿 | 数据变更回调中执行了耗时/UI 操作 | 回调中不做阻塞操作,仅记录标记,UI 更新抛给业务层 |
| 设备协同库不同步 | 未声明 ohos.permission.DISTRIBUTED_DATASYNC;设备未组网;autoSync 关闭 | 声明权限并运行时授权;确认设备已组网;需要时手动调用 sync() |
| KV-Store 打开数量超限 | 单应用同时打开超过 16 个数据库 | 复用 storeId,及时 closeKVStore 释放 |
小结:三种持久化方案各有定位——Preferences 管配置、RDB 管表数据、KV-Store 管分布式键值。先按"数据结构复杂度 + 是否需要跨设备同步"选型,再针对各自的坑(flush、ResultSet 释放、同步权限)做好防护,即可避免绝大多数数据丢失与异常问题。
更多推荐
所有评论(0)