手机上编辑笔记,平板上实时看到更新——不需要手动同步,不需要轮询接口,修改一个属性就自动同步到组网设备。HarmonyOS NEXT 的 distributedDataObject 做到了这一点:像操作普通对象一样操作分布式数据,同步零感知。这篇把 create、setSessionId、on(‘change’) 的完整链路讲清楚。

分布式数据对象概览

核心概念:

  • distributedDataObject——分布式数据对象工厂,创建可同步的数据对象
  • sessionId——会话标识,相同 sessionId 的对象自动同步
  • on(‘change’)——数据变更监听,远端修改后本地自动回调
  • on(‘status’)——状态监听,设备上下线通知
import { distributedDataObject } from '@kit.DistributedDataKit'

在这里插入图片描述

权限与前置条件

{
  "requestPermissions": [
    { "name": "ohos.permission.DISTRIBUTED_DATASYNC", "reason": "$string:distributed_reason" }
  ]
}

前置条件(缺一不可):

  1. 同一华为账号
  2. 蓝牙或 WiFi 连接
  3. 同一局域网
  4. 两端都安装了同一应用

创建分布式数据对象

1. 定义数据结构

interface NoteData {
  content: string;
  title: string;
  lastEditTime: string;
  scrollY: number;
}

2. 创建对象实例

let initData: NoteData = {
  content: '',
  title: '未命名笔记',
  lastEditTime: new Date().toLocaleString(),
  scrollY: 0
}

let distributedObj: distributedDataObject.DataObject = distributedDataObject.create(getContext(), initData)

要点: create 的第二个参数是初始数据,对象的属性名和类型决定了同步的数据结构。只有简单类型(string、number、boolean)和复杂类型(嵌套对象)可以同步,function 和 symbol 不行。

加入会话组网

// 加入会话,相同 sessionId 的对象自动同步
let sessionId: string = 'note_session_001'
distributedObj.setSessionId(sessionId)

// 退出会话,停止同步
distributedObj.setSessionId('')

要点: setSessionId 是组网的核心——两端用同一个 sessionId 就能同步。sessionId 为空字符串则退出组网。不同应用可以用不同的 sessionId 隔离数据。

监听数据变更

distributedObj.on('change', (sessionId: string, fields: string[]) => {
  console.info('数据变更,来自会话: ' + sessionId)
  console.info('变更的字段: ' + fields.join(', '))

  // 读取最新值
  let currentContent: string = distributedObj['content'] as string
  let currentTitle: string = distributedObj['title'] as string
  let currentScrollY: number = distributedObj['scrollY'] as number

  // 更新 UI
  this.content = currentContent
  this.title = currentTitle
  this.scrollY = currentScrollY
})

要点: fields 数组告诉你哪些字段变了,不需要全量刷新。sessionId 标识变更来源,可以区分本地修改还是远端修改。

监听设备状态

distributedObj.on('status', (sessionId: string, networkId: string, status: string) => {
  if (status === 'online') {
    console.info('设备上线: ' + networkId)
    // 设备上线,可以开始同步
  } else if (status === 'offline') {
    console.info('设备离线: ' + networkId)
    // 设备离线,停止同步
  }
})

要点: status 回调只在设备上下线时触发,不是每次数据变更都触发。

修改数据自动同步

// 修改属性 → 自动同步到组网设备
distributedObj['content'] = '这是跨设备同步的内容'
distributedObj['lastEditTime'] = new Date().toLocaleString()
distributedObj['scrollY'] = 256

要点: 直接修改对象属性即可触发同步,不需要手动调用 send/sync 方法。同步是自动的、实时的。

持久化保存

分布式数据对象默认只在内存中,应用退出就丢失。save() 可以持久化到本地:

distributedObj.save('note_backup', (err: BusinessError, status: distributedDataObject.SaveSuccessResponse) => {
  if (err) {
    console.error('保存失败: ' + err.message)
    return
  }
  console.info('保存成功, version: ' + status.version)
})

// 恢复
distributedObj.revokeSave((err: BusinessError, status: distributedDataObject.RevokeSaveSuccessResponse) => {
  console.info('已撤销持久化')
})

要点: save 后数据写入本地数据库,下次 create 时会自动加载持久化的值。revokeSave 撤销持久化,数据回到纯内存模式。

实战:跨设备笔记同步

import { distributedDataObject } from '@kit.DistributedDataKit'

interface NoteData {
  content: string;
  lastEditTime: string;
  scrollY: number;
}

@Entry
@Component
struct DistributedNoteDemo {
  @State content: string = ''
  @State scrollY: number = 0
  @State syncStatus: string = '未组网'
  private distributedObj: distributedDataObject.DataObject | null = null

  aboutToAppear(): void {
    let initData: NoteData = {
      content: '',
      lastEditTime: '',
      scrollY: 0
    }
    this.distributedObj = distributedDataObject.create(getContext(), initData)

    this.distributedObj.on('change', (sessionId: string, fields: string[]) => {
      if (fields.indexOf('content') >= 0) {
        this.content = this.distributedObj['content'] as string
      }
      if (fields.indexOf('scrollY') >= 0) {
        this.scrollY = this.distributedObj['scrollY'] as number
      }
    })
  }

  joinSession(): void {
    if (this.distributedObj !== null) {
      this.distributedObj.setSessionId('note_sync_001')
      this.syncStatus = '已组网'
    }
  }

  onContentChange(value: string): void {
    this.content = value
    if (this.distributedObj !== null) {
      this.distributedObj['content'] = value
      this.distributedObj['lastEditTime'] = new Date().toLocaleString()
    }
  }

  aboutToDisappear(): void {
    if (this.distributedObj !== null) {
      this.distributedObj.setSessionId('')
      this.distributedObj.off('change')
      this.distributedObj.off('status')
    }
  }
}

数据同步 vs 分布式KV Store

对比 distributedDataObject 分布式KV Store
使用方式 像普通对象一样操作 键值对 put/get
同步粒度 属性级 KV 条目级
上手难度
适用场景 简单数据协同 结构化数据存储
持久化 save/revokeSave 自动持久化
冲突处理 后写覆盖 可自定义冲突策略

要点: 简单同步场景用 distributedDataObject,复杂存储场景用 KV Store。

完整 Demo 代码

Demo 模拟了分布式数据对象的创建、组网、同步和离网全过程。

interface SyncMessage {
  id: string;
  content: string;
  fromDevice: string;
  time: string;
}

@Entry
@Component
struct DistributedDataDemo {
  @State localText: string = '';
  @State syncMessages: SyncMessage[] = [];
  @State isOnline: boolean = false;
  @State connectedDevices: string[] = [];
  @State syncStatus: string = '未连接';
  @State sessionId: string = 'session_001';

  build() {
    Column({ space: 0 }) {
      Row() {
        Button('< 返回')
          .fontSize(14)
          .backgroundColor(Color.Transparent)
          .fontColor('#1a73e8')
          .onClick(() => { router.back(); })
        Text('分布式数据对象')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
          .textAlign(TextAlign.Center)
        Row({ space: 4 }) {
          Column()
            .width(8).height(8).borderRadius(4)
            .backgroundColor(this.isOnline ? '#4CAF50' : '#E0E0E0')
          Text(this.isOnline ? '已组网' : '未组网')
            .fontSize(12).fontColor('#999999')
        }
      }
      .width('100%').height(56)
      .padding({ left: 12, right: 12 })
      .alignItems(VerticalAlign.Center)
      .backgroundColor('#FFFFFF')

      Scroll() {
        Column({ space: 16 }) {
          Column({ space: 12 }) {
            Text('分布式数据对象同步')
              .fontSize(16).fontWeight(FontWeight.Bold).width('100%')
            Text('同应用跨设备数据协同,修改即同步')
              .fontSize(13).fontColor('#999999').width('100%')

            TextInput({ text: this.localText, placeholder: '输入内容,模拟同步' })
              .width('100%').height(44)
              .onChange((value: string) => {
                this.localText = value;
                if (this.isOnline) { this.simulateSync(value); }
              })

            Row({ space: 8 }) {
              Button(this.isOnline ? '已组网' : '创建并组网')
                .backgroundColor(this.isOnline ? '#4CAF50' : '#1a73e8')
                .onClick(() => { this.joinNetwork(); })
              Button('离网')
                .backgroundColor('#F44336')
                .enabled(this.isOnline)
                .onClick(() => { this.leaveNetwork(); })
            }

            Text('SessionId: ' + this.sessionId)
              .fontSize(12).fontColor('#999999')
          }
          .width('100%').padding(16).borderRadius(12).backgroundColor('#FFFFFF')

          if (this.syncMessages.length > 0) {
            Column({ space: 8 }) {
              Text('同步记录')
                .fontSize(16).fontWeight(FontWeight.Bold).width('100%')
              ForEach(this.syncMessages.slice().reverse(), (msg: SyncMessage) => {
                Row({ space: 8 }) {
                  Text(msg.time).fontSize(11).fontColor('#999999').width(60)
                  Text(msg.fromDevice).fontSize(12).fontColor('#1a73e8').width(60)
                  Text(msg.content).fontSize(13).fontColor('#333333').layoutWeight(1)
                    .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
                }
                .width('100%').padding(6)
              }, (msg: SyncMessage) => msg.id)
            }
            .width('100%').padding(16).borderRadius(12).backgroundColor('#FFFFFF')
          }
        }
        .padding(16)
      }
      .layoutWeight(1).width('100%')
    }
    .width('100%').height('100%').backgroundColor('#F5F5F5')
  }

  private joinNetwork(): void {
    this.isOnline = true
    this.syncStatus = '已加入组网'
    this.connectedDevices = ['华为Mate 60', '华为MatePad Pro']
    this.syncMessages.push({
      id: Date.now().toString(),
      content: '已加入分布式组网',
      fromDevice: '本机',
      time: new Date().toLocaleTimeString()
    })
  }

  private leaveNetwork(): void {
    this.isOnline = false
    this.syncStatus = '已离网'
    this.connectedDevices = []
  }

  private simulateSync(text: string): void {
    if (!text) return
    this.syncMessages.push({
      id: Date.now().toString(),
      content: text,
      fromDevice: '本机',
      time: new Date().toLocaleTimeString()
    })
    setTimeout(() => {
      this.syncMessages.push({
        id: (Date.now() + 1).toString(),
        content: '[已同步] ' + text,
        fromDevice: '华为Mate 60',
        time: new Date().toLocaleTimeString()
      })
    }, 500)
  }
}

踩坑清单

问题 原因 解决
create 报错 权限未声明 添加 DISTRIBUTED_DATASYNC 权限
setSessionId 不生效 两端不在同一账号/网络 确认同账号 + 蓝牙/WiFi
on(‘change’) 不回调 对端未 setSessionId 两端都要加入同一 sessionId
同步延迟大 网络环境差 蓝牙距离近,WiFi 带宽高
属性修改不同步 属性不是简单类型 只支持 string/number/boolean/对象
修改嵌套对象不同步 嵌套属性需整体赋值 整体替换嵌套对象引用
save 失败 版本冲突 先 revokeSave 再 save
数据丢失 未 save 持久化 应用退出前调用 save
off(‘change’) 报错 监听未注册 先 on 再 off,配对使用
多设备同步冲突 后写覆盖 业务层做版本号判断
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐