HarmonyOS 新手入门:ArkData 键值型数据库,比 Preferences 更像一个小仓库
ArkData 键值型数据库:比 Preferences 更像一个小仓库
上一篇写了 Preferences,它适合存昵称、主题、字号这类小配置。
这篇继续看 ArkData 里的键值型数据库,也就是 distributedKVStore。它同样是 key-value 思路,但比 Preferences 更适合放业务数据,比如草稿、缓存、离线任务状态这类“有一点量,但又不想上关系型数据库”的数据。

官方文档可以先收着:
先分清它和 Preferences
Preferences 更像设置项文件。
KVStore 更像一个本地 key-value 数据库。
我一般会这么区分:
- App 设置、用户偏好:用
Preferences - 草稿箱、搜索历史、临时缓存、离线状态:可以考虑
KVStore - 复杂列表、多条件查询、多表关系:别硬塞,直接看关系型数据库
新手最容易混的就是:看到它们都能 put/get,就觉得差不多。其实场景不一样,Preferences 更轻,KVStore 更偏业务数据存储。
核心流程
键值型数据库比 Preferences 多一步:先创建 KVManager,再拿具体的 KVStore。
流程大概是这样:
createKVManager:创建管理器。getKVStore:打开或创建一个键值数据库。put/get/delete:写入、读取、删除数据。
最小初始化代码:
const context = getContext(this) as common.UIAbilityContext;
const kvManager = distributedKVStore.createKVManager({
context: context,
bundleName: context.abilityInfo.bundleName
});
const options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: false,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S1
};
const store = await kvManager.getKVStore<distributedKVStore.SingleKVStore>(
'demo_note_store',
options
);
这里先用 SINGLE_VERSION,新手入门更好理解。等后面真的要做跨设备协同,再去看 DEVICE_COLLABORATION。
读写数据
写入一条草稿:
await store.put('draft_title', '我的第一条草稿');
await store.put('draft_content', '今天先把 KVStore 跑通。');
读取:
const title = await store.get('draft_title') as string;
const content = await store.get('draft_content') as string;
删除:
await store.delete('draft_title');
await store.delete('draft_content');
和 Preferences 不一样,这里没有 flush() 这一步。KVStore 的 put 本身就是数据库写入操作。
适合做什么 Demo
如果你要给新手讲 KVStore,我建议别一上来就讲分布式同步。
先做一个“草稿箱”最容易理解:
- 输入标题
- 输入内容
- 点击保存,写入 KVStore
- 点击读取,打开半模态面板展示数据库里保存的 key-value 信息
- 重新进入页面,自动读出上次保存的草稿
- 点击清空,删除对应 key
这个场景比“保存主题模式”更像业务数据,也更能体现 KVStore 和 Preferences 的区别。
小坑提醒
storeId 建议只用字母、数字、下划线,比如 demo_note_store。
securityLevel 必填,入门 Demo 可以先用 S1。如果存的是敏感数据,就不要随便照抄这个级别,要按数据敏感程度重新选。
get 读取不存在的 key 时要做好异常处理。新手写 Demo 时可以先用 try/catch 包起来,不然页面上看起来像是“没反应”。
评论区聊聊
你们会把什么数据放进键值型数据库?
草稿、搜索历史、离线缓存,还是一些接口结果?如果你在真实项目里用过 KVStore,也可以聊聊你是怎么设计 key 的。这个点挺实用,后续写 Demo 时可以直接拿真实场景来展开。
完整页面代码
ArkDataKVStoreDemo.ets
import { common } from '@kit.AbilityKit';
import { distributedKVStore } from '@kit.ArkData';
import { promptAction, router } from '@kit.ArkUI';
const STORE_ID: string = 'demo_note_store';
const KEY_TITLE: string = 'draft_title';
const KEY_CONTENT: string = 'draft_content';
class SavedInfoItem {
label: string = '';
value: string = '';
constructor(label: string, value: string) {
this.label = label;
this.value = value;
}
}
@Entry
@Component
struct ArkDataKVStoreDemo {
@State title: string = '';
@State content: string = '';
@State tips: string = '启动后自动读取草稿';
@State showResultSheet: boolean = false;
@State savedItems: SavedInfoItem[] = [];
private store: distributedKVStore.SingleKVStore | undefined = undefined;
aboutToAppear(): void {
this.loadDraft();
}
private async getStore(): Promise<distributedKVStore.SingleKVStore> {
if (this.store !== undefined) {
return this.store;
}
const context = getContext(this) as common.UIAbilityContext;
const kvManager = distributedKVStore.createKVManager({
context: context,
bundleName: context.abilityInfo.bundleName
});
const options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: false,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S1
};
this.store = await kvManager.getKVStore<distributedKVStore.SingleKVStore>(
STORE_ID,
options
);
return this.store;
}
private async readStringValue(store: distributedKVStore.SingleKVStore, key: string): Promise<string> {
try {
return await store.get(key) as string;
} catch (err) {
return '';
}
}
private updateSavedItems(): void {
this.savedItems = [
new SavedInfoItem('数据库 Store ID', STORE_ID),
new SavedInfoItem('标题 Key', KEY_TITLE),
new SavedInfoItem('标题 Value', this.title.length > 0 ? this.title : '未保存'),
new SavedInfoItem('内容 Key', KEY_CONTENT),
new SavedInfoItem('内容 Value', this.content.length > 0 ? this.content : '未保存')
];
}
private async loadDraft(): Promise<void> {
const store = await this.getStore();
this.title = await this.readStringValue(store, KEY_TITLE);
this.content = await this.readStringValue(store, KEY_CONTENT);
this.updateSavedItems();
this.tips = this.title.length > 0 || this.content.length > 0 ?
'读取成功:这是上次保存的草稿' :
'当前还没有保存草稿';
}
private async showSavedInfo(): Promise<void> {
await this.loadDraft();
this.showResultSheet = true;
}
private async saveDraft(): Promise<void> {
try {
const store = await this.getStore();
const title = this.title.length > 0 ? this.title : '未命名草稿';
await store.put(KEY_TITLE, title);
await store.put(KEY_CONTENT, this.content);
this.title = title;
this.updateSavedItems();
this.tips = '保存成功:KVStore 写入后不需要 flush';
promptAction.showToast({ message: '草稿已保存' });
} catch (err) {
this.tips = '保存失败,请查看日志';
promptAction.showToast({ message: '保存失败' });
console.error('KVStore save failed');
}
}
private async deleteKey(store: distributedKVStore.SingleKVStore, key: string): Promise<void> {
try {
await store.delete(key);
} catch (err) {
console.info(`KVStore key not found: ${key}`);
}
}
private async clearDraft(): Promise<void> {
try {
const store = await this.getStore();
await this.deleteKey(store, KEY_TITLE);
await this.deleteKey(store, KEY_CONTENT);
this.title = '';
this.content = '';
this.updateSavedItems();
this.tips = '已清空草稿';
promptAction.showToast({ message: '草稿已清空' });
} catch (err) {
this.tips = '清空失败,请查看日志';
promptAction.showToast({ message: '清空失败' });
console.error('KVStore clear failed');
}
}
@Builder
private savedInfoSheet() {
Column({ space: 14 }) {
Text('数据库读取结果')
.width('100%')
.fontSize(22)
.fontWeight(700)
.fontColor('#0F172A')
Text('下面列表只展示当前 Demo 已保存到 KVStore 的数据库信息,不做 LazyForEach 懒加载渲染处理。')
.width('100%')
.fontSize(13)
.fontColor('#64748B')
Column({ space: 8 }) {
ForEach(this.savedItems, (item: SavedInfoItem) => {
Column({ space: 4 }) {
Text(item.label)
.fontSize(12)
.fontColor('#64748B')
Text(item.value)
.fontSize(16)
.fontColor('#0F172A')
}
.width('100%')
.padding(12)
.backgroundColor('#F8FAFC')
.borderRadius(8)
}, (item: SavedInfoItem) => item.label)
}
.width('100%')
Button('关闭')
.width('100%')
.height(44)
.fontSize(16)
.backgroundColor('#0EA5E9')
.onClick(() => {
this.showResultSheet = false;
})
}
.width('100%')
.padding({ left: 20, right: 20, top: 12, bottom: 20 })
}
build() {
Scroll() {
Column({ space: 18 }) {
Row() {
Text('返回')
.fontSize(14)
.fontColor('#0EA5E9')
.onClick(() => {
router.back();
})
Blank()
}
.width('100%')
Text('ArkData KVStore')
.width('100%')
.fontSize(28)
.fontWeight(700)
.fontColor('#182431')
Text('用键值型数据库保存一份本地草稿。')
.width('100%')
.fontSize(14)
.fontColor('#56616F')
Column({ space: 12 }) {
Text('草稿标题')
.fontSize(14)
.fontColor('#334155')
TextInput({ placeholder: '输入标题', text: this.title })
.height(44)
.fontSize(16)
.backgroundColor('#F1F5F9')
.fontColor('#0F172A')
.onChange((value: string) => {
this.title = value;
})
}
.width('100%')
Column({ space: 12 }) {
Text('草稿内容')
.fontSize(14)
.fontColor('#334155')
TextArea({ placeholder: '输入草稿内容', text: this.content })
.height(140)
.fontSize(16)
.backgroundColor('#F1F5F9')
.fontColor('#0F172A')
.onChange((value: string) => {
this.content = value;
})
}
.width('100%')
Text(this.title.length > 0 ? this.title : '未命名草稿')
.width('100%')
.padding(16)
.fontSize(18)
.fontWeight(700)
.fontColor('#0F172A')
.backgroundColor('#E0F2FE')
.borderRadius(8)
Text(this.content.length > 0 ? this.content : '这里会预览草稿内容')
.width('100%')
.padding(16)
.fontSize(15)
.fontColor('#334155')
.backgroundColor('#F8FAFC')
.borderRadius(8)
Row({ space: 12 }) {
Button('保存')
.layoutWeight(1)
.height(44)
.fontSize(16)
.backgroundColor('#0EA5E9')
.onClick(() => {
this.saveDraft();
})
Button('读取')
.layoutWeight(1)
.height(44)
.fontSize(16)
.fontColor('#0F172A')
.backgroundColor('#BAE6FD')
.onClick(() => {
this.showSavedInfo();
})
Button('清空')
.layoutWeight(1)
.height(44)
.fontSize(16)
.fontColor('#0F172A')
.backgroundColor('#CBD5E1')
.onClick(() => {
this.clearDraft();
})
}
.width('100%')
Text(this.tips)
.width('100%')
.fontSize(13)
.fontColor('#64748B')
}
.width('100%')
.padding(24)
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
.bindSheet(this.showResultSheet, this.savedInfoSheet(), {
height: SheetSize.MEDIUM,
dragBar: true,
showClose: true,
onDisappear: () => {
this.showResultSheet = false;
}
})
}
}
- 点赞
- 收藏
- 关注作者
评论(0)