HarmonyOS 新手入门:ArkData 分布式数据对象,像改变量一样同步页面状态

举报
蓝瘦的蜕变 发表于 2026/06/25 10:21:27 2026/06/25
【摘要】 前面我们写过 DeviceKVStore,也写过应用接续。分布式数据对象看起来也在做“跨设备同步”,但它的定位不一样。 它不是持久化数据库,更像把一个 JS 对象封装成可同步的共享状态。你在一个设备上改对象属性,同一个 sessionId 下的另一台设备可以收到变化。 官方文档可以先放在手边: 分布式数据对象跨设备数据

HarmonyOS 新手入门:ArkData 分布式数据对象,像改变量一样同步页面状态

前面我们写过 DeviceKVStore,也写过应用接续。分布式数据对象看起来也在做“跨设备同步”,但它的定位不一样。

它不是持久化数据库,更像把一个 JS 对象封装成可同步的共享状态。你在一个设备上改对象属性,同一个 sessionId 下的另一台设备可以收到变化。

官方文档可以先放在手边:

先说结论

KVStore 更像一个键值仓库,适合保存草稿、设置、轻量业务状态。

分布式数据对象更像“正在协同的页面状态”。官方文档里强调,它目前主要用于跨端迁移和跨设备 Call 多端协同场景。

适合这些:

  • 多端协同时同步标题、光标、进度、开关状态。
  • 跨端迁移时把当前页面状态交给接收端恢复。
  • 小体量对象的字段变化监听。

不适合这些:

  • 当成本地数据库长期保存大量数据。
  • 同步大列表或大文件内容。
  • 在没有迁移或跨设备 Call 场景时强行做复杂协同。

一句话:要长期保存,用 KVStore / RDB;要同步当前协同状态,再看分布式数据对象。

它怎么用

最小链路是这样:

create(context, source) -> genSessionId() -> setSessionId(sessionId) -> on('change') / on('status')

如果是跨端迁移,还会用到:

save(deviceId) -> 接收端用同一个 sessionId 恢复 -> revokeSave()

这里最重要的是 sessionId。不同设备上的分布式数据对象,只有加入同一个 sessionId,才会进入同一份共享对象数据。

这个 Demo 做什么

页面会做几件事:

  • 用当前标题、内容、进度创建一个分布式数据对象。
  • 生成 sessionId
  • 调用 setSessionId(sessionId) 加入同步关系。
  • 注册 changestatus 监听。
  • 填写远端设备 ID 后调用 save(deviceId)
  • 用半模态展示对象快照、sessionId、监听反馈和保存状态。

单机可以看清楚对象创建和 session 流程。真正同步需要跨端迁移或跨设备 Call 场景,以及可信组网环境。

容易踩坑的地方

第一,它不是 Preferences,也不是 RDB。数据对象本身不是为了长期持久化设计的。

第二,同步最小单位是属性。对象里某个根属性变化时,同步的是这个属性。

第三,复杂类型不要想得太自由。官方文档说明,复杂类型目前主要支持根属性修改;如果是资产类型,还有单独的资产同步机制。

欢迎评论区一起分享~

如果你做多端协同,你会先同步哪些页面状态?标题、进度、选中项、临时筛选条件,还是正在编辑的表单字段?这些状态适合做成一个分布式数据对象,还是更适合落到 KVStore / RDB?

完整示例代码

文件位置:ArkDataDistributedObjectDemo.ets

import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
import { distributedDataObject } from '@kit.ArkData';
import { promptAction, router } from '@kit.ArkUI';

const DISTRIBUTED_PERMISSION: Permissions = 'ohos.permission.DISTRIBUTED_DATASYNC';

class DistributedDraftSource {
  title: string = '';
  content: string = '';
  progress: number = 0;
  updatedAt: string = '';

  constructor(title: string, content: string, progress: number, updatedAt: string) {
    this.title = title;
    this.content = content;
    this.progress = progress;
    this.updatedAt = updatedAt;
  }
}

class DataObjectInfoItem {
  label: string = '';
  value: string = '';

  constructor(label: string, value: string) {
    this.label = label;
    this.value = value;
  }
}

@Entry
@Component
struct ArkDataDistributedObjectDemo {
  @State title: string = '跨端草稿';
  @State content: string = '分布式数据对象适合同步轻量页面状态。';
  @State progress: number = 40;
  @State sessionId: string = '';
  @State remoteDeviceId: string = '';
  @State tips: string = '创建分布式数据对象后,多个设备使用同一个 sessionId 才能建立同步关系。';
  @State changeTips: string = '尚未收到对象字段变化通知。';
  @State statusTips: string = '尚未收到对象上下线通知。';
  @State infoItems: DataObjectInfoItem[] = [];
  @State showResultSheet: boolean = false;

  private dataObject: distributedDataObject.DataObject | undefined = undefined;
  private changeObserver: ((sessionId: string, fields: string[]) => void) | undefined = undefined;
  private statusObserver: ((sessionId: string, networkId: string, status: 'online' | 'offline') => void) | undefined =
    undefined;

  aboutToAppear(): void {
    this.updateInfoItems('初始化页面');
  }

  aboutToDisappear(): void {
    const dataObject = this.dataObject;
    if (dataObject !== undefined) {
      const changeObserver = this.changeObserver;
      const statusObserver = this.statusObserver;
      if (changeObserver !== undefined) {
        dataObject.off('change', changeObserver);
      }
      if (statusObserver !== undefined) {
        dataObject.off('status', statusObserver);
      }
    }
  }

  private buildSource(): DistributedDraftSource {
    return new DistributedDraftSource(
      this.title.length > 0 ? this.title : '未命名草稿',
      this.content,
      this.progress,
      new Date().toString()
    );
  }

  private updateInfoItems(action: string): void {
    this.infoItems = [
      new DataObjectInfoItem('最近动作', action),
      new DataObjectInfoItem('对象标题', this.title.length > 0 ? this.title : '未填写'),
      new DataObjectInfoItem('对象内容', this.content.length > 0 ? this.content : '未填写'),
      new DataObjectInfoItem('同步进度', `${this.progress}%`),
      new DataObjectInfoItem('sessionId', this.sessionId.length > 0 ? this.sessionId : '未加入 session'),
      new DataObjectInfoItem('远端设备 ID', this.remoteDeviceId.length > 0 ? this.remoteDeviceId : '未填写'),
      new DataObjectInfoItem('数据变化监听', this.changeTips),
      new DataObjectInfoItem('状态监听', this.statusTips),
      new DataObjectInfoItem('适用场景', '跨端迁移、跨设备 Call 多端协同'),
      new DataObjectInfoItem('限制提醒', '对象同步不是持久化数据库,大小和设备数量都有约束')
    ];
  }

  private async requestDistributedPermission(): Promise<boolean> {
    try {
      const context = getContext(this) as common.UIAbilityContext;
      const atManager = abilityAccessCtrl.createAtManager();
      const result = await atManager.requestPermissionsFromUser(context, [DISTRIBUTED_PERMISSION]);
      const granted = result.authResults.length > 0 &&
        result.authResults[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
      if (!granted) {
        this.tips = '未获得分布式数据同步权限,页面本地预览仍可继续。';
        this.updateInfoItems('权限未授权');
      }
      return granted;
    } catch (err) {
      this.tips = '申请分布式数据同步权限失败,页面本地预览仍可继续。';
      this.updateInfoItems('权限申请失败');
      console.error('Distributed object permission request failed');
      return false;
    }
  }

  private async createObject(): Promise<boolean> {
    try {
      const granted = await this.requestDistributedPermission();
      if (!granted) {
        promptAction.showToast({ message: '未获得分布式权限' });
        return false;
      }

      const oldObject = this.dataObject;
      if (oldObject !== undefined) {
        const oldChangeObserver = this.changeObserver;
        const oldStatusObserver = this.statusObserver;
        if (oldChangeObserver !== undefined) {
          oldObject.off('change', oldChangeObserver);
        }
        if (oldStatusObserver !== undefined) {
          oldObject.off('status', oldStatusObserver);
        }
        this.changeObserver = undefined;
        this.statusObserver = undefined;
      }

      const context = getContext(this) as common.UIAbilityContext;
      this.dataObject = distributedDataObject.create(context, this.buildSource());
      this.registerObservers();
      this.tips = '分布式数据对象已创建,下一步生成 sessionId 并加入 session。';
      this.updateInfoItems('创建 DataObject');
      return true;
    } catch (err) {
      this.tips = '创建分布式数据对象失败,请查看日志。';
      promptAction.showToast({ message: '创建失败' });
      console.error('Distributed data object create failed');
      return false;
    }
  }

  private registerObservers(): void {
    const dataObject = this.dataObject;
    if (dataObject === undefined || this.changeObserver !== undefined || this.statusObserver !== undefined) {
      return;
    }

    const changeObserver = (sessionId: string, fields: string[]) => {
      this.changeTips = `session=${sessionId},变化字段数:${fields.length}`;
      this.updateInfoItems('收到对象变化');
    };
    const statusObserver = (sessionId: string, networkId: string, status: 'online' | 'offline') => {
      this.statusTips = `session=${sessionId},networkId=${networkId},status=${status}`;
      this.updateInfoItems('收到对象状态变化');
    };

    dataObject.on('change', changeObserver);
    dataObject.on('status', statusObserver);
    this.changeObserver = changeObserver;
    this.statusObserver = statusObserver;
  }

  private createSession(): void {
    try {
      this.sessionId = distributedDataObject.genSessionId();
      this.tips = 'sessionId 已生成。另一台设备使用同一个 sessionId 才会进入同一份共享对象数据。';
      this.updateInfoItems('生成 sessionId');
      promptAction.showToast({ message: 'sessionId 已生成' });
    } catch (err) {
      this.tips = '生成 sessionId 失败。';
      promptAction.showToast({ message: '生成失败' });
      console.error('Generate sessionId failed');
    }
  }

  private async joinSession(): Promise<void> {
    if (this.sessionId.length === 0) {
      this.createSession();
    }

    const dataObject = this.dataObject;
    if (dataObject === undefined) {
      const created = await this.createObject();
      if (!created || this.dataObject === undefined) {
        return;
      }
    }

    try {
      const currentObject = this.dataObject;
      if (currentObject === undefined) {
        return;
      }
      await currentObject.setSessionId(this.sessionId);
      this.tips = '已加入 session。可信组网内同 sessionId 的对象可以自动同步属性变化。';
      this.updateInfoItems('加入 session');
      promptAction.showToast({ message: '已加入 session' });
    } catch (err) {
      this.tips = '加入 session 失败,请确认权限和分布式环境。';
      promptAction.showToast({ message: '加入失败' });
      console.error('Join distributed object session failed');
    }
  }

  private async leaveSession(): Promise<void> {
    const dataObject = this.dataObject;
    if (dataObject === undefined) {
      return;
    }

    try {
      await dataObject.setSessionId();
      this.tips = '已退出当前 session,对象退回本地数据对象状态。';
      this.updateInfoItems('退出 session');
      promptAction.showToast({ message: '已退出 session' });
    } catch (err) {
      this.tips = '退出 session 失败。';
      promptAction.showToast({ message: '退出失败' });
      console.error('Leave distributed object session failed');
    }
  }

  private async refreshLocalSnapshot(): Promise<void> {
    const created = await this.createObject();
    if (!created) {
      return;
    }
    const dataObject = this.dataObject;
    if (dataObject !== undefined && this.sessionId.length > 0) {
      await dataObject.setSessionId(this.sessionId);
    }
    this.tips = '已用当前页面输入重新创建对象快照。真机协同时,字段变化会通过同 sessionId 的对象同步。';
    this.updateInfoItems('更新对象快照');
    promptAction.showToast({ message: '对象快照已更新' });
  }

  private async saveToRemote(): Promise<void> {
    const dataObject = this.dataObject;
    const deviceId = this.remoteDeviceId.trim();
    if (deviceId.length === 0) {
      this.tips = '请先填写远端设备 ID。';
      this.updateInfoItems('保存参数缺失');
      promptAction.showToast({ message: '请填写设备 ID' });
      return;
    }

    if (dataObject === undefined) {
      const created = await this.createObject();
      if (!created || this.dataObject === undefined) {
        return;
      }
    }

    try {
      const currentObject = this.dataObject;
      if (currentObject === undefined) {
        return;
      }
      await currentObject.save(deviceId);
      this.tips = '已调用 save 保存对象。跨端迁移场景下,接收端可按 sessionId 恢复数据。';
      this.updateInfoItems('save 到远端设备');
      promptAction.showToast({ message: '已调用 save' });
    } catch (err) {
      this.tips = 'save 调用失败,请确认设备能力和迁移场景。';
      this.updateInfoItems('save 失败');
      promptAction.showToast({ message: 'save 失败' });
      console.error('Distributed data object save failed');
    }
  }

  private async revokeRemoteSave(): Promise<void> {
    const dataObject = this.dataObject;
    if (dataObject === undefined) {
      return;
    }

    try {
      await dataObject.revokeSave();
      this.tips = '已撤回已保存的分布式数据对象。';
      this.updateInfoItems('revokeSave');
      promptAction.showToast({ message: '已撤回保存' });
    } catch (err) {
      this.tips = '撤回保存失败,请查看日志。';
      this.updateInfoItems('revokeSave 失败');
      promptAction.showToast({ message: '撤回失败' });
      console.error('Distributed data object revokeSave failed');
    }
  }

  private showObjectInfo(): void {
    this.updateInfoItems('查看对象信息');
    this.showResultSheet = true;
  }

  @Builder
  private objectInfoSheet() {
    Column({ space: 14 }) {
      Text('分布式数据对象信息')
        .width('100%')
        .fontSize(22)
        .fontWeight(700)
        .fontColor('#0F172A')

      Text('下面展示对象快照、sessionId、监听反馈和保存状态。列表只展示当前 Demo 信息,不做 LazyForEach 懒加载渲染处理。')
        .width('100%')
        .fontSize(13)
        .fontColor('#64748B')

      Column({ space: 8 }) {
        ForEach(this.infoItems, (item: DataObjectInfoItem) => {
          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: DataObjectInfoItem) => item.label)
      }
      .width('100%')

      Button('关闭')
        .width('100%')
        .height(44)
        .fontSize(16)
        .backgroundColor('#7C3AED')
        .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('#7C3AED')
            .onClick(() => {
              router.back();
            })
          Blank()
        }
        .width('100%')

        Text('分布式数据对象')
          .width('100%')
          .fontSize(28)
          .fontWeight(700)
          .fontColor('#182431')

        Text('把页面状态封装成对象,用同一个 sessionId 在可信设备之间同步轻量数据。')
          .width('100%')
          .fontSize(14)
          .fontColor('#56616F')

        TextInput({ placeholder: '输入标题', text: this.title })
          .height(44)
          .fontSize(16)
          .backgroundColor('#F5F3FF')
          .fontColor('#0F172A')
          .onChange((value: string) => {
            this.title = value;
          })

        TextArea({ placeholder: '输入内容', text: this.content })
          .height(110)
          .fontSize(16)
          .backgroundColor('#F5F3FF')
          .fontColor('#0F172A')
          .onChange((value: string) => {
            this.content = value;
          })

        Row() {
          Text('同步进度')
            .fontSize(16)
            .fontColor('#0F172A')
            .layoutWeight(1)
          Text(`${this.progress}%`)
            .fontSize(16)
            .fontWeight(700)
            .fontColor('#7C3AED')
        }
        .width('100%')

        Slider({ value: this.progress, min: 0, max: 100, step: 5 })
          .onChange((value: number) => {
            this.progress = value;
          })

        TextInput({ placeholder: '真机组网后填写远端 deviceId', text: this.remoteDeviceId })
          .height(44)
          .fontSize(16)
          .backgroundColor('#EDE9FE')
          .fontColor('#0F172A')
          .onChange((value: string) => {
            this.remoteDeviceId = value;
            this.updateInfoItems('更新设备 ID');
          })

        Row({ space: 10 }) {
          Button('生成 session')
            .layoutWeight(1)
            .height(44)
            .fontSize(15)
            .backgroundColor('#7C3AED')
            .onClick(() => {
              this.createSession();
            })
          Button('加入')
            .layoutWeight(1)
            .height(44)
            .fontSize(15)
            .fontColor('#FFFFFF')
            .backgroundColor('#0F766E')
            .onClick(() => {
              this.joinSession();
            })
          Button('退出')
            .layoutWeight(1)
            .height(44)
            .fontSize(15)
            .fontColor('#0F172A')
            .backgroundColor('#DDD6FE')
            .onClick(() => {
              this.leaveSession();
            })
        }
        .width('100%')

        Row({ space: 10 }) {
          Button('更新对象')
            .layoutWeight(1)
            .height(44)
            .fontSize(15)
            .fontColor('#0F172A')
            .backgroundColor('#EDE9FE')
            .onClick(() => {
              this.refreshLocalSnapshot();
            })
          Button('save')
            .layoutWeight(1)
            .height(44)
            .fontSize(15)
            .fontColor('#FFFFFF')
            .backgroundColor('#6D28D9')
            .onClick(() => {
              this.saveToRemote();
            })
          Button('撤回')
            .layoutWeight(1)
            .height(44)
            .fontSize(15)
            .fontColor('#0F172A')
            .backgroundColor('#CBD5E1')
            .onClick(() => {
              this.revokeRemoteSave();
            })
        }
        .width('100%')

        Button('查看对象信息')
          .width('100%')
          .height(44)
          .fontSize(15)
          .fontColor('#0F172A')
          .backgroundColor('#F3E8FF')
          .onClick(() => {
            this.showObjectInfo();
          })

        Column({ space: 6 }) {
          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.sessionId.length > 0 ? `sessionId: ${this.sessionId}` : '尚未生成 sessionId')
            .width('100%')
            .fontSize(12)
            .fontColor('#64748B')
        }
        .width('100%')
        .padding(16)
        .backgroundColor('#F8FAFC')
        .borderRadius(8)

        Text(this.tips)
          .width('100%')
          .fontSize(13)
          .fontColor('#64748B')

        Text(this.changeTips)
          .width('100%')
          .fontSize(13)
          .fontColor('#7C3AED')

        Text(this.statusTips)
          .width('100%')
          .fontSize(13)
          .fontColor('#0F766E')
      }
      .width('100%')
      .padding(24)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
    .bindSheet(this.showResultSheet, this.objectInfoSheet(), {
      height: SheetSize.MEDIUM,
      dragBar: true,
      showClose: true,
      onDisappear: () => {
        this.showResultSheet = false;
      }
    })
  }
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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