HarmonyOS 新手入门:ArkData 分布式键值型数据库,让草稿在多设备之间流转
HarmonyOS 新手入门:ArkData 分布式键值型数据库,让草稿在多设备之间流转
上一篇我们用 SINGLE_VERSION 做了一个本地草稿箱。它已经能保存、读取、清空,但数据只在当前设备里。
如果场景变成“手机写一半,平板继续写”,就可以继续看 DEVICE_COLLABORATION。它还是键值数据库,只是数据会带上设备维度,并且可以在可信设备之间同步。
官方文档先放这里:
先说结论
DeviceKVStore 适合“轻量、多端、状态同步”的数据。
- 多设备草稿:手机写标题,平板补正文。
- 阅读进度:手机看到第 8 页,平板打开继续第 8 页。
- 小型任务状态:待办、设备协同开关、临时同步状态。
不太适合的场景也要讲清楚:大量列表、复杂查询、多表关系,还是别硬塞到 KVStore 里。
它怎么用
本地 KVStore 用的是 SINGLE_VERSION:
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION
分布式场景换成 DEVICE_COLLABORATION:
kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION
然后再补三件事。
第一,module.json5 里声明 ohos.permission.DISTRIBUTED_DATASYNC,并补上 reason、usedScene。
权限配置可以这样写:
"requestPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC",
"reason": "$string:distributed_datasync_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
}
]
第二,打开同步能力,比如 enableSync(true)。
第三,监听 dataChange,方便看到本机或远端的数据变化。
这个 Demo 做什么
这次 Demo 仍然做草稿,只是加了一个“远端设备 ID”输入框。
单设备调试时,你可以验证:
- 保存草稿。
- 读取草稿。
- 半模态弹框展示完整保存信息。
- 清空草稿。
多设备调试时,再填入远端 deviceId,点击同步,触发:
store.sync([deviceId], distributedKVStore.SyncMode.PUSH_PULL, 0);
注意:分布式同步不是模拟器里随便点一下就能看到效果。它需要可信设备、组网环境、权限和同一个应用数据环境。单机 Demo 能跑通本地逻辑,多设备效果要上真机验证。
容易踩坑的地方
新手不要一上来就写“自动发现设备 + 复杂同步策略”。先把这条链路跑通:
DeviceKVStore 创建 -> put/get -> dataChange 监听 -> 手动 sync
这条链路明白后,再去加设备管理、冲突处理、同步状态展示,学习成本会低很多。
欢迎评论区一起分享~
你们一般会把 DeviceKVStore 用在哪些多设备场景里?草稿、阅读进度、待办同步,还是设备协同状态?可以在评论区聊聊,我后面可以继续按真实场景扩展 Demo。
完整示例代码
文件路径:ArkDataDistributedKVStoreDemo.ets
import { common } from '@kit.AbilityKit';
import { distributedKVStore } from '@kit.ArkData';
import { promptAction, router } from '@kit.ArkUI';
const STORE_ID: string = 'demo_distributed_note_store';
const KEY_TITLE: string = 'draft_title';
const KEY_CONTENT: string = 'draft_content';
const KEY_UPDATED_AT: string = 'draft_updated_at';
class DbInfoItem {
label: string = '';
value: string = '';
constructor(label: string, value: string) {
this.label = label;
this.value = value;
}
}
@Entry
@Component
struct ArkDataDistributedKVStoreDemo {
@State title: string = '';
@State content: string = '';
@State updatedAt: string = '';
@State remoteDeviceId: string = '';
@State tips: string = '单机可验证本地读写,组网后可测试跨设备同步';
@State syncTips: string = '等待同步操作';
@State changeTips: string = '尚未收到数据变更通知';
@State showResultSheet: boolean = false;
@State savedItems: DbInfoItem[] = [];
private store: distributedKVStore.DeviceKVStore | undefined = undefined;
private changeListener: ((change: distributedKVStore.ChangeNotification) => void) |
undefined = undefined;
aboutToAppear(): void {
this.initPage();
}
aboutToDisappear(): void {
const listener = this.changeListener;
if (this.store !== undefined && listener !== undefined) {
this.store.off('dataChange', listener);
this.changeListener = undefined;
}
}
private async initPage(): Promise<void> {
await this.loadLocalDraft();
await this.subscribeDataChange();
}
private async getStore(): Promise<distributedKVStore.DeviceKVStore> {
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: true,
kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
securityLevel: distributedKVStore.SecurityLevel.S1
};
const store = await kvManager.getKVStore<distributedKVStore.DeviceKVStore>(
STORE_ID,
options
);
this.store = store;
try {
await store.enableSync(true);
await store.setSyncParam(0);
} catch (err) {
this.syncTips = '同步参数设置失败,请确认分布式环境和权限';
}
return store;
}
private valueToText(value: boolean | string | number | Uint8Array): string {
if (typeof value === 'string') {
return value;
}
if (typeof value === 'number') {
return value.toString();
}
if (typeof value === 'boolean') {
return value ? 'true' : 'false';
}
return 'Uint8Array';
}
private async readStringValue(store: distributedKVStore.DeviceKVStore, key: string): Promise<string> {
try {
const value = await store.get(key);
return this.valueToText(value);
} catch (err) {
return '';
}
}
private updateSavedItems(): void {
this.savedItems = [
new DbInfoItem('数据库 Store ID', STORE_ID),
new DbInfoItem('数据库类型', 'DEVICE_COLLABORATION'),
new DbInfoItem('自动同步', 'autoSync: true'),
new DbInfoItem('标题 Key', KEY_TITLE),
new DbInfoItem('标题 Value', this.title.length > 0 ? this.title : '未保存'),
new DbInfoItem('内容 Key', KEY_CONTENT),
new DbInfoItem('内容 Value', this.content.length > 0 ? this.content : '未保存'),
new DbInfoItem('更新时间 Key', KEY_UPDATED_AT),
new DbInfoItem('更新时间 Value', this.updatedAt.length > 0 ? this.updatedAt : '未保存'),
new DbInfoItem('远端设备 ID', this.remoteDeviceId.length > 0 ? this.remoteDeviceId : '未填写'),
new DbInfoItem('同步反馈', this.syncTips),
new DbInfoItem('变更监听', this.changeTips)
];
}
private async loadLocalDraft(): Promise<void> {
const store = await this.getStore();
this.title = await this.readStringValue(store, KEY_TITLE);
this.content = await this.readStringValue(store, KEY_CONTENT);
this.updatedAt = await this.readStringValue(store, KEY_UPDATED_AT);
this.updateSavedItems();
this.tips = this.title.length > 0 || this.content.length > 0 ?
'读取成功:当前展示的是本机设备下的草稿' :
'当前本机还没有保存草稿';
}
private async subscribeDataChange(): Promise<void> {
if (this.changeListener !== undefined) {
return;
}
const store = await this.getStore();
const listener = (change: distributedKVStore.ChangeNotification) => {
const count = change.insertEntries.length + change.updateEntries.length + change.deleteEntries.length;
this.changeTips = `收到 ${count} 条数据变更,来源设备:${change.deviceId}`;
this.loadLocalDraft();
};
this.changeListener = listener;
store.on(
'dataChange',
distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL,
listener
);
}
private async saveDraft(): Promise<void> {
try {
const store = await this.getStore();
const title = this.title.length > 0 ? this.title : '未命名分布式草稿';
const now = new Date().toString();
await store.put(KEY_TITLE, title);
await store.put(KEY_CONTENT, this.content);
await store.put(KEY_UPDATED_AT, now);
this.title = title;
this.updatedAt = now;
this.tips = '保存成功:已写入 DeviceKVStore';
this.syncTips = '已写入本机,可信设备在线时可继续触发同步';
this.updateSavedItems();
promptAction.showToast({ message: '分布式草稿已保存' });
} catch (err) {
this.tips = '保存失败,请查看日志';
promptAction.showToast({ message: '保存失败' });
console.error('Distributed KVStore save failed');
}
}
private async syncToRemote(): Promise<void> {
const deviceId = this.remoteDeviceId.trim();
if (deviceId.length === 0) {
this.syncTips = '请先填写远端设备 ID';
this.updateSavedItems();
promptAction.showToast({ message: '请填写远端设备 ID' });
return;
}
try {
const store = await this.getStore();
store.sync([deviceId], distributedKVStore.SyncMode.PUSH_PULL, 0);
this.syncTips = '已触发 PUSH_PULL 同步,结果以远端设备和监听反馈为准';
this.updateSavedItems();
promptAction.showToast({ message: '已触发同步' });
} catch (err) {
this.syncTips = '同步触发失败,请检查设备 ID、组网和权限';
this.updateSavedItems();
promptAction.showToast({ message: '同步失败' });
console.error('Distributed KVStore sync failed');
}
}
private async deleteKey(store: distributedKVStore.DeviceKVStore, key: string): Promise<void> {
try {
await store.delete(key);
} catch (err) {
console.info(`Distributed 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);
await this.deleteKey(store, KEY_UPDATED_AT);
this.title = '';
this.content = '';
this.updatedAt = '';
this.tips = '已清空本机草稿';
this.syncTips = '清空后可再次同步到可信设备';
this.updateSavedItems();
promptAction.showToast({ message: '草稿已清空' });
} catch (err) {
this.tips = '清空失败,请查看日志';
promptAction.showToast({ message: '清空失败' });
console.error('Distributed KVStore clear failed');
}
}
private async showSavedInfo(): Promise<void> {
await this.loadLocalDraft();
this.showResultSheet = true;
}
@Builder
private savedInfoSheet() {
Column({ space: 14 }) {
Text('分布式数据库读取结果')
.width('100%')
.fontSize(22)
.fontWeight(700)
.fontColor('#0F172A')
Text('下面列表只展示当前 Demo 保存到 DeviceKVStore 的数据库信息,不做 LazyForEach 懒加载渲染处理。')
.width('100%')
.fontSize(13)
.fontColor('#64748B')
Column({ space: 8 }) {
ForEach(this.savedItems, (item: DbInfoItem) => {
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: DbInfoItem) => item.label)
}
.width('100%')
Button('关闭')
.width('100%')
.height(44)
.fontSize(16)
.backgroundColor('#2563EB')
.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('#2563EB')
.onClick(() => {
router.back();
})
Blank()
}
.width('100%')
Text('分布式 KVStore')
.width('100%')
.fontSize(28)
.fontWeight(700)
.fontColor('#182431')
Text('把本地草稿升级为可跨设备同步的 DeviceKVStore 示例。')
.width('100%')
.fontSize(14)
.fontColor('#56616F')
Column({ space: 10 }) {
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: 10 }) {
Text('草稿内容')
.fontSize(14)
.fontColor('#334155')
TextArea({ placeholder: '输入草稿内容', text: this.content })
.height(120)
.fontSize(16)
.backgroundColor('#F1F5F9')
.fontColor('#0F172A')
.onChange((value: string) => {
this.content = value;
})
}
.width('100%')
Column({ space: 10 }) {
Text('远端设备 ID')
.fontSize(14)
.fontColor('#334155')
TextInput({ placeholder: '真机组网后填写远端 deviceId', text: this.remoteDeviceId })
.height(44)
.fontSize(16)
.backgroundColor('#EFF6FF')
.fontColor('#0F172A')
.onChange((value: string) => {
this.remoteDeviceId = value;
this.updateSavedItems();
})
}
.width('100%')
Column({ space: 8 }) {
Text(this.title.length > 0 ? this.title : '未命名分布式草稿')
.width('100%')
.fontSize(18)
.fontWeight(700)
.fontColor('#0F172A')
Text(this.content.length > 0 ? this.content : '这里会预览草稿内容')
.width('100%')
.fontSize(15)
.fontColor('#334155')
Text(this.updatedAt.length > 0 ? `更新时间:${this.updatedAt}` : '尚未保存')
.width('100%')
.fontSize(12)
.fontColor('#64748B')
}
.width('100%')
.padding(16)
.backgroundColor('#F8FAFC')
.borderRadius(8)
Row({ space: 10 }) {
Button('保存')
.layoutWeight(1)
.height(44)
.fontSize(15)
.backgroundColor('#2563EB')
.onClick(() => {
this.saveDraft();
})
Button('读取')
.layoutWeight(1)
.height(44)
.fontSize(15)
.fontColor('#0F172A')
.backgroundColor('#BFDBFE')
.onClick(() => {
this.showSavedInfo();
})
}
.width('100%')
Row({ space: 10 }) {
Button('同步')
.layoutWeight(1)
.height(44)
.fontSize(15)
.fontColor('#FFFFFF')
.backgroundColor('#0F766E')
.onClick(() => {
this.syncToRemote();
})
Button('清空')
.layoutWeight(1)
.height(44)
.fontSize(15)
.fontColor('#0F172A')
.backgroundColor('#CBD5E1')
.onClick(() => {
this.clearDraft();
})
}
.width('100%')
Text(this.tips)
.width('100%')
.fontSize(13)
.fontColor('#64748B')
Text(this.syncTips)
.width('100%')
.fontSize(13)
.fontColor('#0F766E')
Text(this.changeTips)
.width('100%')
.fontSize(13)
.fontColor('#2563EB')
}
.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)