HarmonyOS 6.0 分布式数据对象——改一个属性就自动同步,跨设备数据协同这么简单
手机上编辑笔记,平板上实时看到更新——不需要手动同步,不需要轮询接口,修改一个属性就自动同步到组网设备。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" }
]
}
前置条件(缺一不可):
- 同一华为账号
- 蓝牙或 WiFi 连接
- 同一局域网
- 两端都安装了同一应用
创建分布式数据对象
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,配对使用 |
| 多设备同步冲突 | 后写覆盖 | 业务层做版本号判断 |
更多推荐

所有评论(0)