搞鸿蒙开发久了就会遇到数据持久化的选择困境:Preferences只适合简单配置项,关系型数据库(RDB)又太重,而像用户收藏列表、本地缓存快照这些结构化数据,偏偏需要一种中间方案。我试过用Preferences硬存JSON,但跨设备同步时发现它根本不支持多端协同。后来啃了KV Store的文档,踩了几个坑才摸清它的正确用法。
KV Store全称是分布式键值数据库,属于ArkData数据管理框架的一部分。它不仅能存复杂结构的K-V数据,还支持多设备同步、Schema校验、事务操作和加密存储。相比Preferences,它的数据格式更稳定,不存在XML和GSKV混用导致兼容崩溃的问题。
如果需求是多端收藏——手机收藏文章后平板和车机也能看到,KV Store就是最佳选择。它提供两种数据库类型:SINGLE_VERSION(单版本,不同设备改同一个Key互相覆盖,适合缓存)和DEVICE_COLLABORATION(多设备协同,每条数据标记来源设备,适合需要区分设备的场景)。收藏功能用SINGLE_VERSION就够了,因为不管从哪个设备写入,最终只需展示最新版本。
一、核心概念与初始化
操作KV Store的第一步不是读写数据,而是创建KVManager。它负责管理所有数据库实例的生命周期。注意context参数必须是BaseContext类型,我一开始传错类型直接崩溃,报错信息也不直观。- import { distributedKVStore } from '@kit.ArkData';
- const kvManagerConfig: distributedKVStore.KVManagerConfig = {
- bundleName: 'com.example.myapp',
- context: getContext(this)
- };
- const kvManager = distributedKVStore.createKVManager(kvManagerConfig);
复制代码
getKVStore是异步操作,必须在回调中拿到store实例后再进行后续读写。我最初犯的错是在外面直接使用kvStore,结果当然是undefined。- const STORE_ID = 'my_favorites_store';
- function getAndOpenStore() {
- const options: distributedKVStore.Options = {
- createIfMissing: true,
- encrypt: false,
- backup: true,
- autoSync: false,
- kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
- securityLevel: distributedKVStore.SecurityLevel.S2,
- };
- kvManager.getKVStore(STORE_ID, options, (err, store) => {
- if (err) return;
- kvStore = store;
- });
- }
复制代码
二、Schema校验的正确姿势
Schema相当于给存入的Value定义了数据结构规范,可以自动校验字段类型、非空约束和默认值。我一开始觉得麻烦,但它挡掉了很多低级bug——比如Key对了但Value格式传错,没有Schema时数据悄咪咪写进去,读出来才一脸懵。构建Schema时注意mode=1表示STRICT模式,skip=0表示不跳过校验。- let idField = new distributedKVStore.FieldNode('id');
- idField.type = distributedKVStore.ValueType.INTEGER;
- idField.nullable = false;
- idField.default = '0';
- let schema = new distributedKVStore.Schema();
- schema.root.appendChild(idField);
- schema.indexes = ['$.id'];
- schema.mode = 1;
复制代码
三、完整CRUD操作
写数据用put,支持string、number、boolean和Uint8Array类型。存复杂对象需先JSON序列化,我习惯用callback写法,Promise写法也可以。- kvStore.put('bookmark:1001', JSON.stringify({
- id: 1001,
- title: '鸿蒙开发实战笔记',
- timestamp: Date.now()
- }), (err) => {
- if (err) console.error(`Put failed: ${err.code}`);
- });
复制代码 读数据建议封装成Promise,方便async/await调用:- function getData(key: string): Promise<string | undefined> {
- return new Promise((resolve, reject) => {
- if (!kvStore) reject(new Error('未初始化'));
- kvStore.get(key, (err, data) => {
- err ? reject(err) : resolve(data as string);
- });
- });
- }
复制代码 批量操作使用putBatch,参数为Entry数组。删除数据和关闭数据库类似,注意closeKVStore和deleteKVStore需要传入bundleName和storeId。
四、数据变化订阅与事务
多设备同步场景下,数据变化订阅很实用。通过kvStore.on('dataChange', subscribeType, callback)注册监听,注意文档强调回调内不能进行阻塞操作(如直接修改UI组件),否则界面会卡死。正确做法是抛事件给UI层处理。- kvStore.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
- console.info(`新增${data.insertEntries.length}条,更新${data.updateEntries.length}条,删除${data.deleteEntries.length}条`);
- // 不要在这里直接操作UI
- });
复制代码 事务操作使用startTransaction、commit和rollback,适合需要保证数据一致性的场景。虽然回调嵌套较深,但可以用Promise封装:- kvStore.startTransaction((err) => {
- if (err) return;
- kvStore.put('key1', 'value1', (err1) => {
- if (err1) { kvStore.rollback(); return; }
- kvStore.put('key2', 'value2', (err2) => {
- if (err2) { kvStore.rollback(); return; }
- kvStore.commit((commitErr) => {
- if (commitErr) console.error(`提交失败: ${commitErr.code}`);
- });
- });
- });
- });
复制代码
五、安全级别与加密
安全级别从S1到S4递增,选择取决于数据类型:S1(性别、国籍)、S2(姓名、详细地址)、S3(精确定位)、S4(生物信息、信用卡等)。一个重要限制:安全等级只能升不能降,建库时设了S2,后续改S3可以,但想降回S2必须删库重建。开启加密只需在Options中设置encrypt: true。- const secureOptions: distributedKVStore.Options = {
- createIfMissing: true,
- encrypt: true,
- backup: true,
- autoSync: false,
- kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
- securityLevel: distributedKVStore.SecurityLevel.S3,
- };
复制代码
六、实战:封装工具类
每个项目都重复写CRUD太原始,我封装了一个KVStoreHelper工具类,将初始化、读写、JSON存取、批量操作统一管理,用Promise简化回调地狱。核心代码如下:- export class KVStoreHelper {
- private kvManager: distributedKVStore.KVManager | null = null;
- private kvStore: distributedKVStore.SingleKVStore | null = null;
- constructor(private bundleName: string, private storeId: string) {}
- async init(): Promise<void> {
- const config = { bundleName: this.bundleName, context: getContext() };
- this.kvManager = distributedKVStore.createKVManager(config);
- const options = {
- createIfMissing: true,
- encrypt: false,
- backup: true,
- autoSync: false,
- kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
- securityLevel: distributedKVStore.SecurityLevel.S2,
- };
- return new Promise((resolve, reject) => {
- this.kvManager!.getKVStore(this.storeId, options, (err, store) => {
- if (err) reject(new Error(`Init failed: ${err.code}`));
- this.kvStore = store;
- resolve();
- });
- });
- }
- async put(key: string, value: string | number | boolean | Uint8Array): Promise<void> {
- if (!this.kvStore) throw new Error('未初始化');
- return new Promise((resolve, reject) => {
- this.kvStore!.put(key, value, (err) => err ? reject(err) : resolve());
- });
- }
- async get(key: string): Promise<string | number | boolean | Uint8Array | undefined> {
- if (!this.kvStore) throw new Error('未初始化');
- return new Promise((resolve, reject) => {
- this.kvStore!.get(key, (err, data) => err ? reject(err) : resolve(data));
- });
- }
- async putJSON(key: string, obj: Record<string, object>): Promise<void> {
- return this.put(key, JSON.stringify(obj));
- }
- async getJSON<T>(key: string): Promise<T | null> {
- const data = await this.get(key);
- if (typeof data === 'string') return JSON.parse(data) as T;
- return null;
- }
- async putBatch(entries: Array<{ key: string; value: string }>): Promise<void> {
- if (!this.kvStore) throw new Error('未初始化');
- const kvEntries = entries.map(e => ({
- key: e.key,
- value: { type: distributedKVStore.ValueType.STRING, value: e.value }
- }));
- return new Promise((resolve, reject) => {
- this.kvStore!.putBatch(kvEntries, (err) => err ? reject(err) : resolve());
- });
- }
- async close(): Promise<void> {
- if (!this.kvManager) return;
- this.kvStore = null;
- return new Promise((resolve, reject) => {
- this.kvManager!.closeKVStore(this.bundleName, this.storeId, (err) => {
- err ? reject(err) : resolve();
- });
- });
- }
- }
复制代码
使用该工具类时,在onPageShow或onWindowStageCreate中先await init(),后续所有操作都是Promise形式,代码更简洁,也更容易集成到MVVM架构中。
总结:KV Store是鸿蒙数据持久化中承上启下的利器,既弥补了Preferences对复杂结构和同步支持的不足,又避免了RDB的笨重。关键要区分好SINGLE_VERSION和DEVICE_COLLABORATION的使用场景,注意异步回调的生命周期,以及安全级别的升降限制。封装好后,在多个项目里复用非常省心。 |