HarmonyOS 新手入门:ArkData 分布式键值型数据库,让草稿在多设备之间流转

举报
蓝瘦的蜕变 发表于 2026/06/24 10:54:39 2026/06/24
【摘要】 上一篇我们用 SINGLEVERSION 做了一个本地草稿箱。它已经能保存、读取、清空,但数据只在当前设备里。 如果场景变成“手机写一半,平板继续写”,就可以继续看 DEVICECOLLABORATION。它还是键值数据库,只是数据会带上设备维度,并且可以在可信设备之间同步。 官方文档先放这里: 通过键值型数据库实现数

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,并补上 reasonusedScene

权限配置可以这样写:

"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;
      }
    })
  }
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。