1# 分布式数据对象跨设备数据同步 2 3 4## 场景介绍 5 6传统方式下,设备之间的数据同步,需要开发者完成消息处理逻辑,包括:建立通信链接、消息收发处理、错误重试、数据冲突解决等操作,工作量非常大。而且设备越多,调试复杂度也将同步增加。 7 8其实设备之间的状态、消息发送进度、发送的数据等都是“变量”。如果这些变量支持“全局”访问,那么开发者跨设备访问这些变量就能像操作本地变量一样,从而能够自动高效、便捷地实现数据多端同步。 9 10分布式数据对象即实现了对“变量”的“全局”访问。向应用开发者提供内存对象的创建、查询、删除、修改、订阅等基本数据对象的管理能力,同时具备分布式能力。为开发者在分布式应用场景下提供简单易用的JS接口,轻松实现多设备间同应用的数据协同,同时设备间可以监听对象的状态和数据变更。满足超级终端场景下,相同应用多设备间的数据对象协同需求。与传统方式相比,分布式数据对象大大减少了开发者的工作量。 11 12 13## 基本概念 14 15- **分布式内存数据库** 16 分布式内存数据库将数据缓存在内存中,以便应用获得更快的数据存取速度,不会将数据进行持久化。若数据库关闭,则数据不会保留。 17 18- **分布式数据对象** 19 分布式数据对象是一个JS对象型的封装。每一个分布式数据对象实例会创建一个内存数据库中的数据表,每个应用程序创建的内存数据库相互隔离,对分布式数据对象的“读取”或“赋值”会自动映射到对应数据库的get/put操作。 20 21 分布式数据对象的生命周期包括以下状态: 22 23 - 未初始化:未实例化,或已被销毁。 24 - 本地数据对象:已创建对应的数据表,但是还无法进行数据同步。 25 - 分布式数据对象:已创建对应的数据表,设备在线且组网内设置同样sessionId的对象数>=2,可以跨设备同步数据。若设备掉线或将sessionId置为空,分布式数据对象退化为本地数据对象。 26 27 28## 运作机制 29 30**图1** 分布式数据对象运作机制 31 32![distributedObject](figures/distributedObject.jpg) 33 34分布式数据对象生长在分布式内存数据库之上,在分布式内存数据库上进行了JS对象型的封装,能像操作本地变量一样操作分布式数据对象,数据的跨设备同步由系统自动完成。 35 36 37### JS对象型存储与封装机制 38 39- 为每个分布式数据对象实例创建一个内存数据库,通过SessionId标识,每个应用程序创建的内存数据库相互隔离。 40 41- 在分布式数据对象实例化的时候,(递归)遍历对象所有属性,使用“Object.defineProperty”定义所有属性的set和get方法,set和get中分别对应数据库一条记录的put和get操作,Key对应属性名,Value对应属性值。 42 43- 在开发者对分布式数据对象进行“读取”或者“赋值”的时候,都会自动调用到set和get方法,映射到对应数据库的操作。 44 45**表1** 分布式数据对象和分布式数据库的对应关系 46 47| 分布式对象实例 | 对象实例 | 属性名称 | 属性值 | 48| -------- | -------- | -------- | -------- | 49| 分布式内存数据库 | 一个数据库(sessionID标识) | 一条数据库记录的key | 一条数据库记录的value | 50 51 52### 跨设备同步和数据变更通知机制 53 54分布式数据对象,最重要的功能就是对象之间的数据同步。可信组网内的设备可以在本地创建分布式数据对象,并设置sessionID。不同设备上的分布式数据对象,通过设置相同的sessionID,建立对象之间的同步关系。 55 56如下图所示,设备A和设备B上的“分布式数据对象1”,其sessionID均为session1,这两个对象建立了session1的同步关系。 57 58 **图2** 对象的同步关系 59 60![distributedObject_sync](figures/distributedObject_sync.jpg) 61 62一个同步关系中,一个设备只能有一个对象加入。比如上图中,设备A的“分布式数据对象1”已经加入了session1的同步关系,所以设备A的“分布式数据对象2”就加入失败了。 63 64建立同步关系后,每个Session有一份共享对象数据。加入了同一个Session的对象,支持以下操作: 65 66(1)读取/修改Session中的数据。 67 68(2)监听数据变更,感知其他设备对共享对象数据的修改。 69 70(3)监听状态变更,感知其他设备的加入和退出。 71 72 73### 同步的最小单位 74 75关于分布式数据对象的数据同步,值得注意的是,同步的最小单位是“属性”。比如,下图中对象1包含三个属性:name、age和parents。当其中一个属性变更时,则数据同步时只需同步此变更的属性。 76 77**图3** 数据同步视图 78 79 80![distributedObject_syncView](figures/distributedObject_syncView.jpg) 81 82 83### 对象持久化缓存机制 84 85分布式对象主要运行在应用程序的进程空间。当调用分布式对象持久化接口时,通过分布式数据库对对象进行持久化和同步,进程退出后数据也不会丢失。 86 87该场景是分布式对象的扩展场景,主要用于以下情况: 88 89- 在设备上创建持久化对象后APP退出,重新打开APP,创建持久化对象,加入同一个Session,数据可以恢复到APP退出前的数据。 90 91- 在设备A上创建持久化对象并同步后持久化到设备B后,A设备的APP退出,设备B打开APP,创建持久化对象,加入同一个Session,数据可以恢复到A设备退出前的数据。 92 93### 资产同步机制 94 95在分布式对象中,可以使用[资产类型](../reference/apis-arkdata/js-apis-data-commonType.md#asset)来描述本地实体资产文件,分布式对象跨设备同步时,该文件会和数据一起同步到其他设备上。当前只支持资产类型,不支持[资产类型数组](../reference/apis-arkdata/js-apis-data-commonType.md#assets)。如需同步多个资产,可将每个资产作为分布式对象的一个根属性实现。 96 97### 融合资产冲突解决机制 98 99当分布式对象中包含的资产和关系型数据库中包含的资产指向同一个实体资产文件,即两个资产的Uri相同时,就会存在冲突,我们把这种资产称为融合资产。若想解决融合资产的冲突,需要先进行资产的绑定。当应用退出session后,绑定关系随之消失。 100 101## 约束限制 102 103- 不同设备间只有相同bundleName的应用才能直接同步。 104 105- 分布式数据对象的数据同步发生在同一个应用程序下,且同sessionID之间。 106 107- 不建议创建过多的分布式数据对象,每个分布式数据对象将占用100-150KB内存。 108 109- 每个分布式数据对象大小不超过500KB。 110 111- 设备A修改1KB数据,设备B收到变更通知,50ms内完成。 112 113- 单个应用程序最多只能创建16个分布式数据对象实例。 114 115- 考虑到性能和用户体验,最多不超过3个设备进行数据协同。 116 117- 如对复杂类型的数据进行修改,仅支持修改根属性,暂不支持下级属性修改。[资产同步机制](#资产同步机制)中,资产类型的数据支持下一级属性修改。 118 119- 支持JS接口间的互通,与其他语言不互通。 120 121## 接口说明 122 123以下是分布式对象跨设备数据同步功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[分布式数据对象](../reference/apis-arkdata/js-apis-data-distributedobject.md)。 124 125 126 127| 接口名称 | 描述 | 128| -------- | -------- | 129| create(context: Context, source: object): DataObject | 创建并得到一个分布式数据对象实例。 | 130| genSessionId(): string | 创建一个sessionId,可作为分布式数据对象的sessionId。 | 131| setSessionId(sessionId: string, callback: AsyncCallback<void>): void | 设置同步的sessionId,当可信组网中有多个设备时,多个设备间的对象如果设置为同一个sessionId,就能自动同步。 | 132| setSessionId(callback: AsyncCallback<void>): void | 退出所有已加入的session。 | 133| on(type: 'change', callback: (sessionId: string, fields: Array<string>) => void): void | 监听分布式数据对象的数据变更。 | 134| off(type: 'change', callback?: (sessionId: string, fields: Array<string>) => void): void | 取消监听分布式数据对象的数据变更。 | 135| on(type: 'status', callback: (sessionId: string, networkId: string, status: 'online' \| 'offline' ) => void): void | 监听分布式数据对象的上下线。 | 136| off(type: 'status', callback?: (sessionId: string, networkId: string, status: 'online' \|'offline' ) => void): void | 取消监听分布式数据对象的上下线。 | 137| save(deviceId: string, callback: AsyncCallback<SaveSuccessResponse>): void | 保存分布式数据对象。 | 138| revokeSave(callback: AsyncCallback<RevokeSaveSuccessResponse>): void | 撤回保存的分布式数据对象。 | 139| bindAssetStore(assetKey: string, bindInfo: BindInfo, callback: AsyncCallback<void>): void | 绑定融合资产。 | 140 141 142## 开发步骤 143 144### 跨设备数据同步 145 146以一次分布式数据对象同步为例,说明开发步骤。 147 1481. 导入`@ohos.data.distributedDataObject`模块。 149 150 ```ts 151 import distributedDataObject from '@ohos.data.distributedDataObject'; 152 ``` 153 1542. 请求权限。 155 156 1. 需要申请ohos.permission.DISTRIBUTED_DATASYNC权限,配置方式请参见[声明权限](../security/AccessToken/declare-permissions.md)。 157 2. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/AccessToken/request-user-authorization.md)。 158 1593. 创建并得到一个分布式数据对象实例。 160 161 Stage模型示例: 162 163 ```ts 164 // 导入模块 165 import distributedDataObject from '@ohos.data.distributedDataObject'; 166 import UIAbility from '@ohos.app.ability.UIAbility'; 167 import { BusinessError } from '@ohos.base'; 168 import window from '@ohos.window'; 169 170 class ParentObject { 171 mother: string 172 father: string 173 174 constructor(mother: string, father: string) { 175 this.mother = mother 176 this.father = father 177 } 178 } 179 class SourceObject { 180 name: string | undefined 181 age: number | undefined 182 isVis: boolean | undefined 183 parent: Object | undefined 184 185 constructor(name: string | undefined, age: number | undefined, isVis: boolean | undefined, parent: ParentObject | undefined) { 186 this.name = name 187 this.age = age 188 this.isVis = isVis 189 this.parent = parent 190 } 191 } 192 193 class EntryAbility extends UIAbility { 194 onWindowStageCreate(windowStage: window.WindowStage) { 195 let parentSource: ParentObject = new ParentObject('jack mom', 'jack Dad'); 196 let source: SourceObject = new SourceObject("jack", 18, false, parentSource); 197 let localObject: distributedDataObject.DataObject = distributedDataObject.create(this.context, source); 198 } 199 } 200 ``` 201 202 FA模型示例: 203 204 ```ts 205 // 导入模块 206 import distributedDataObject from '@ohos.data.distributedDataObject'; 207 import featureAbility from '@ohos.ability.featureAbility'; 208 // 获取context 209 let context = featureAbility.getContext(); 210 class ParentObject { 211 mother: string 212 father: string 213 constructor(mother: string, father: string) { 214 this.mother = mother 215 this.father = father 216 } 217 } 218 class SourceObject { 219 name: string | undefined 220 age: number | undefined 221 isVis: boolean | undefined 222 parent: ParentObject | undefined 223 constructor(name: string | undefined, age: number | undefined, isVis: boolean | undefined, parent: ParentObject | undefined) { 224 this.name = name 225 this.age = age 226 this.isVis = isVis 227 this.parent = parent 228 } 229 } 230 let parentSource: ParentObject = new ParentObject('jack mom', 'jack Dad'); 231 let source: SourceObject = new SourceObject("jack", 18, false, parentSource); 232 // 创建对象,该对象包含4个属性类型:string、number、boolean和Object 233 let localObject: distributedDataObject.DataObject = distributedDataObject.create(context, source); 234 ``` 235 2364. 加入同步组网。同步组网中的数据对象分为发起方和被拉起方。 237 238 ```ts 239 // 设备1加入sessionId 240 let sessionId: string = '123456'; 241 242 localObject.setSessionId(sessionId); 243 244 // 和设备1协同的设备2加入同一个session 245 246 // 创建对象,该对象包含4个属性类型:string、number、boolean和Object 247 let remoteSource: SourceObject = new SourceObject(undefined, undefined, undefined, undefined); 248 let remoteObject: distributedDataObject.DataObject = distributedDataObject.create(this.context, remoteSource); 249 // 收到status上线后remoteObject同步数据,即name变成jack,age变成18 250 remoteObject.setSessionId(sessionId); 251 ``` 252 2535. 监听对象数据变更。可监听对端数据的变更,以callback作为变更回调实例。 254 255 ```ts 256 localObject.on("change", (sessionId: string, fields: Array<string>) => { 257 console.info("change" + sessionId); 258 if (fields != null && fields != undefined) { 259 for (let index: number = 0; index < fields.length; index++) { 260 console.info(`The element ${localObject[fields[index]]} changed.`); 261 } 262 } 263 }); 264 ``` 265 2666. 修改对象属性,对象属性支持基本类型(数字类型、布尔类型、字符串类型)以及复杂类型(数组、基本类型嵌套等)。 267 268 ```ts 269 localObject["name"] = 'jack1'; 270 localObject["age"] = 19; 271 localObject["isVis"] = false; 272 let parentSource1: ParentObject = new ParentObject('jack1 mom', 'jack1 Dad'); 273 localObject["parent"] = parentSource1; 274 ``` 275 276 > **说明:** 277 > 278 > 针对复杂类型的数据修改,目前仅支持对根属性的修改,暂不支持对下级属性的修改。 279 280 281 ```ts 282 // 支持的修改方式 283 let parentSource1: ParentObject = new ParentObject('mom', 'Dad'); 284 localObject["parent"] = parentSource1; 285 // 不支持的修改方式 286 localObject["parent"]["mother"] = 'mom'; 287 ``` 288 2897. 访问对象。可以通过直接获取的方式访问到分布式数据对象的属性,且该数据为组网内的最新数据。 290 291 ```ts 292 console.info(`name:${localObject['name']}`); 293 ``` 294 2958. 删除监听数据变更。可以指定删除监听的数据变更回调;也可以不指定,这将会删除该分布式数据对象的所有数据变更回调。 296 297 ```ts 298 // 删除变更回调 299 localObject.off('change', (sessionId: string, fields: Array<string>) => { 300 console.info("change" + sessionId); 301 if (fields != null && fields != undefined) { 302 for (let index: number = 0; index < fields.length; index++) { 303 console.info("changed !" + fields[index] + " " + localObject[fields[index]]); 304 } 305 } 306 }); 307 // 删除所有的变更回调 308 localObject.off('change'); 309 ``` 310 3119. 监听分布式数据对象的上下线。可以监听对端分布式数据对象的上下线。 312 313 ```ts 314 localObject.on('status', (sessionId: string, networkId: string, status: 'online' | 'offline') => { 315 console.info("status changed " + sessionId + " " + status + " " + networkId); 316 // 业务处理 317 }); 318 ``` 319 32010. 保存和撤回已保存的数据对象。 321 322 ```ts 323 // 保存数据对象,如果应用退出后组网内设备需要恢复对象数据时调用 324 localObject.save("local").then((result: distributedDataObject.SaveSuccessResponse) => { 325 console.info(`Succeeded in saving. SessionId:${result.sessionId},version:${result.version},deviceId:${result.deviceId}`); 326 }).catch((err: BusinessError) => { 327 console.error(`Failed to save. Code:${err.code},message:${err.message}`); 328 }); 329 330 // 撤回保存的数据对象 331 localObject.revokeSave().then((result: distributedDataObject.RevokeSaveSuccessResponse) => { 332 console.info(`Succeeded in revokeSaving. Session:${result.sessionId}`); 333 }).catch((err: BusinessError) => { 334 console.error(`Failed to revokeSave. Code:${err.code},message:${err.message}`); 335 }); 336 ``` 337 33811. 删除监听分布式数据对象的上下线。可以指定删除监听的上下线回调;也可以不指定,这将会删除该分布式数据对象的所有上下线回调。 339 340 ```ts 341 // 删除上下线回调 342 localObject.off('status', (sessionId: string, networkId: string, status: 'online' | 'offline') => { 343 console.info("status changed " + sessionId + " " + status + " " + networkId); 344 // 业务处理 345 }); 346 // 删除所有的上下线回调 347 localObject.off('status'); 348 ``` 349 35012. 退出同步组网。分布式数据对象退出组网后,本地的数据变更对端不会同步。 351 352 ```ts 353 localObject.setSessionId(() => { 354 console.info('leave all session.'); 355 }); 356 ``` 357 358### 跨设备资产同步 359 360分布式对象中加入资产类型属性,可以触发资产同步机制,将资产类型属性所描述的文件同步到其他设备。持有资产文件的设备为发起端,得到资产文件的设备为接收端。 361 3621. 导入`@ohos.data.distributedDataObject`和`@ohos.data.commonType`模块。 363 364 ```ts 365 import distributedDataObject from '@ohos.data.distributedDataObject'; 366 import commonType from '@ohos.data.commonType'; 367 ``` 368 3692. 请求权限。 370 371 1. 需要申请ohos.permission.DISTRIBUTED_DATASYNC权限,配置方式请参见[声明权限](../security/AccessToken/declare-permissions.md)。 372 2. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/AccessToken/request-user-authorization.md)。 373 3743. 发起端创建包含资产的分布式对象并加入组网。 375 376 ```ts 377 import UIAbility from '@ohos.app.ability.UIAbility'; 378 import type window from '@ohos.window'; 379 import distributedDataObject from '@ohos.data.distributedDataObject'; 380 import commonType from '@ohos.data.commonType'; 381 import type { BusinessError } from '@ohos.base'; 382 383 class Note { 384 title: string | undefined 385 text: string | undefined 386 attachment: commonType.Asset | undefined 387 388 constructor(title: string | undefined, text: string | undefined, attachment: commonType.Asset | undefined) { 389 this.title = title; 390 this.text = text; 391 this.attachment = attachment; 392 } 393 } 394 395 class EntryAbility extends UIAbility { 396 onWindowStageCreate(windowStage: window.WindowStage) { 397 let attachment: commonType.Asset = { 398 name: 'test_img.jpg', 399 uri: 'file://com.example.myapplication/data/storage/el2/distributedfiles/dir/test_img.jpg', 400 path: '/dir/test_img.jpg', 401 createTime: '2024-01-02 10:00:00', 402 modifyTime: '2024-01-02 10:00:00', 403 size: '5', 404 status: commonType.AssetStatus.ASSET_NORMAL 405 } 406 // 创建一个自定义笔记类型,其中包含一张图片资产 407 let note: Note = new Note('test', "test", attachment); 408 let localObject: distributedDataObject.DataObject = distributedDataObject.create(this.context, note); 409 localObject.setSessionId('123456'); 410 } 411 } 412 ``` 413 4144. 接收端创建分布式对象并加入组网 415 416 ```ts 417 let note: Note = new Note(undefined, undefined, undefined); 418 let receiverObject: distributedDataObject.DataObject = distributedDataObject.create(this.context, note); 419 receiverObject.on('change', (sessionId: string, fields: Array<string>) => { 420 if (fields.includes('attachment')) { 421 // 接收端监听到资产类型属性的数据变更时,代表其所描述的资产文件同步完成 422 console.info('attachment synchronization completed'); 423 } 424 }); 425 receiverObject.setSessionId('123456'); 426 ``` 427 4285. 若资产为融合资产,可以创建绑定信息,绑定融合资产,以解决融合资产的冲突。 429 430 ```ts 431 const bindInfo: distributedDataObject.BindInfo = { 432 storeName: 'notepad', 433 tableName: 'note_t', 434 primaryKey: { 435 'uuid': '00000000-0000-0000-0000-000000000000' 436 }, 437 field: 'attachment', 438 assetName: attachment.name 439 } 440 441 localObject.bindAssetStore('attachment', bindInfo, (err: BusinessError) => { 442 if (err) { 443 console.error('bindAssetStore failed.'); 444 } 445 console.info('bindAssetStore success.'); 446 }); 447 ``` 448 449## 相关实例 450 451针对分布式数据对象开发,有以下相关实例可供参考: 452 453- [分布式组网认证(ArkTS)(Full SDK)(API10)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-4.0-Release/code/SuperFeature/DistributedAppDev/DistributedAuthentication) 454 455- [分布式对象(ArkTS)(Full SDK)(API10)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-4.0-Release/code/SuperFeature/DistributedAppDev/DistributedNote)