HarmonyOS NEXT 数据持久化三剑客:Preferences、RelationalStore 与 KVDB 选型实战

HarmonyOS NEXT 数据持久化三剑客:Preferences、RelationalStore 与 KVDB 选型实战

HarmonyOS NEXT 数据持久化三剑客

前言

在移动应用开发中,数据持久化是核心基础能力。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 在需要时打通设备边界。


参考资料

相关推荐
richard_yuu1 小时前
鸿蒙从零搭建参赛项目|心晴驿站:开发环境配置、技术选型与项目规范落地
华为·harmonyos
shaodong11231 小时前
鸿蒙自定义弹窗(CustomDialog)的 8 种封装姿势
华为·harmonyos
xmdy58663 小时前
Flutter + 开源鸿蒙跨端实战|基于空间地理信息的**城市全域智慧泊车调度与多维运维管理平台** Day1 项目架构基座与工程化环境搭建
flutter·开源·harmonyos
枫叶丹44 小时前
【HarmonyOS 6.0】状态栏扩展新特性:点击状态栏图标展开二级菜单的场景动效详解
开发语言·华为·harmonyos
想你依然心痛5 小时前
HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与Face AR & Body AR的“灵犀筑境“——PC端沉浸式AR建筑空间评审系统
华为·ar·harmonyos·悬浮导航·沉浸光感
xmdy58665 小时前
Flutter+开源鸿蒙全域智慧泊车调度管理平台 Day4 订单全流程闭环+支付核验+会员权益+个人中心开发
flutter·开源·harmonyos
前端不太难5 小时前
鸿蒙 App 多端 UI 不一致的原因
ui·状态模式·harmonyos
说再见再也见不到5 小时前
华为AC+AP旁挂二层组网+直接转发,配置实战
网络·华为·交换机·无线组网·无线ac
key_3_feng6 小时前
鸿蒙6.0电子手表高山攀登指标监测功能开发实战
华为·harmonyos