一、前言 & 背景

开发 OpenHarmony 应用时,数据存内存里会碰到三个典型痛点:

  1. 进程被杀数据就没了:用户配置的 WiFi 名称、设备参数、开关状态,App 一退出全部丢失,每次都要重新配置;
  2. 跨页面/跨启动无法共享:A 页面保存的配置,B 页面和下次启动都拿不到;
  3. 查询效率低:设备列表、历史记录这类结构化数据,如果每次启动都重新扫描/重新组装,体验很差。

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 释放、同步权限)做好防护,即可避免绝大多数数据丢失与异常问题。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐