1# 分布式数据对象跨设备数据同步
2
3
4## 场景介绍
5
6传统方式下,设备之间的数据同步,需要开发者完成消息处理逻辑,包括:建立通信链接、消息收发处理、错误重试、数据冲突解决等操作,工作量非常大。而且设备越多,调试复杂度也将同步增加。
7
8其实设备之间的状态、消息发送进度、发送的数据等都是“变量”。如果这些变量支持“全局”访问,那么开发者跨设备访问这些变量就能像操作本地变量一样,从而能够自动高效、便捷地实现数据多端同步。
9
10分布式数据对象即实现了对“变量”的“全局”访问。向应用开发者提供内存对象的创建、查询、删除、修改、订阅等基本数据对象的管理能力,同时具备分布式能力。为开发者在分布式应用场景下提供简单易用的JS接口,轻松实现多设备间同应用的数据协同,同时设备间可以监听对象的状态和数据变更。满足超级终端场景下,相同应用多设备间的数据对象协同需求。与传统方式相比,分布式数据对象大大减少了开发者的工作量。
11
12目前<!--RP2-->分布式数据对象只能在[跨端迁移](../application-models/hop-cross-device-migration.md)和[通过跨设备Call调用实现的多端协同](../application-models/hop-multi-device-collaboration.md#通过跨设备call调用实现多端协同)场景中使用。<!--RP2End-->
13
14## 基本概念
15
16- **分布式内存数据库**
17  分布式内存数据库将数据缓存在内存中,以便应用获得更快的数据存取速度,不会将数据进行持久化。若数据库关闭,则数据不会保留。
18
19- **分布式数据对象**
20  分布式数据对象是一个JS对象型的封装。每一个分布式数据对象实例会创建一个内存数据库中的数据表,每个应用程序创建的内存数据库相互隔离,对分布式数据对象的“读取”或“赋值”会自动映射到对应数据库的get/put操作。
21
22  分布式数据对象的生命周期包括以下状态:
23
24  - 未初始化:未实例化,或已被销毁。
25  - 本地数据对象:已创建对应的数据表,但是还无法进行数据同步。
26  - 分布式数据对象:已创建对应的数据表,设备在线且组网内设置同样sessionId的对象数&gt;=2,可以跨设备同步数据。若设备掉线或将sessionId置为空,分布式数据对象退化为本地数据对象。
27
28
29## 运作机制
30
31**图1** 分布式数据对象运作机制 
32
33![distributedObject](figures/distributedObject.jpg)
34
35分布式数据对象生长在分布式内存数据库之上,在分布式内存数据库上进行了JS对象型的封装,能像操作本地变量一样操作分布式数据对象,数据的跨设备同步由系统自动完成。
36
37
38### JS对象型存储与封装机制
39
40- 为每个分布式数据对象实例创建一个内存数据库,通过SessionId标识,每个应用程序创建的内存数据库相互隔离。
41
42- 在分布式数据对象实例化的时候,(递归)遍历对象所有属性,使用“Object.defineProperty”定义所有属性的set和get方法,set和get中分别对应数据库一条记录的put和get操作,Key对应属性名,Value对应属性值。
43
44- 在开发者对分布式数据对象进行“读取”或者“赋值”的时候,都会自动调用到set和get方法,映射到对应数据库的操作。
45
46**表1** 分布式数据对象和分布式数据库的对应关系
47
48| 分布式对象实例 | 对象实例 | 属性名称 | 属性值 |
49| -------- | -------- | -------- | -------- |
50| 分布式内存数据库 | 一个数据库(sessionID标识) | 一条数据库记录的key | 一条数据库记录的value |
51
52
53### 跨设备同步和数据变更通知机制
54
55分布式数据对象,最重要的功能就是对象之间的数据同步。可信组网内的设备可以在本地创建分布式数据对象,并设置sessionID。不同设备上的分布式数据对象,通过设置相同的sessionID,建立对象之间的同步关系。
56
57如下图所示,设备A和设备B上的“分布式数据对象1”,其sessionID均为session1,这两个对象建立了session1的同步关系。
58
59  **图2** 对象的同步关系  
60
61![distributedObject_sync](figures/distributedObject_sync.jpg)
62
63一个同步关系中,一个设备只能有一个对象加入。比如上图中,设备A的“分布式数据对象1”已经加入了session1的同步关系,所以设备A的“分布式数据对象2”就加入失败了。
64
65建立同步关系后,每个Session有一份共享对象数据。加入了同一个Session的对象,支持以下操作:
66
67(1)读取/修改Session中的数据。
68
69(2)监听数据变更,感知其他设备对共享对象数据的修改。
70
71(3)监听状态变更,感知其他设备的加入和退出。
72
73分布式数据对象加入session时,如果它的数据与session中的数据不同,则它会更新session中的数据。如果希望分布式数据对象加入sessionId时不更新session中的数据,并且得到session中的数据,需要将对象的属性的值设置为undefined(资产类型的属性则是将它的各个属性值设置为空字符串)。
74
75### 同步的最小单位
76
77关于分布式数据对象的数据同步,值得注意的是,同步的最小单位是“属性”。比如,下图中对象1包含三个属性:name、age和parents。当其中一个属性变更时,则数据同步时只需同步此变更的属性。
78
79对象属性支持基本类型(数字类型、布尔类型、字符串类型)以及复杂类型(数组、基本类型嵌套)。针对复杂类型的数据修改,目前仅支持对根属性的修改,暂不支持对下级属性的修改。
80
81```ts
82dataObject['parents'] = {mom: "amy"}; // 支持的修改
83dataObject['parents']['mon'] = "amy"; // 不支持的修改
84```
85
86**图3** 数据同步视图 
87
88
89![distributedObject_syncView](figures/distributedObject_syncView.jpg)
90
91
92### 对象持久化缓存机制
93
94分布式对象主要运行在应用程序的进程空间。当调用分布式对象持久化接口时,通过分布式数据库对对象进行持久化和同步,进程退出后数据也不会丢失。
95
96该场景是分布式对象的扩展场景,主要用于以下情况:
97
98- 在设备上创建持久化对象后APP退出,重新打开APP,创建持久化对象,加入同一个Session,数据可以恢复到APP退出前的数据。
99
100- 在设备A上创建持久化对象并同步后持久化到设备B后,A设备的APP退出,设备B打开APP,创建持久化对象,加入同一个Session,数据可以恢复到A设备退出前的数据。
101
102### 资产同步机制
103
104在分布式对象中,可以使用[资产类型](../reference/apis-arkdata/js-apis-data-commonType.md#asset)来描述本地实体资产文件,分布式对象跨设备同步时,该文件会和数据一起同步到其他设备上。当前只支持资产类型,不支持[资产类型数组](../reference/apis-arkdata/js-apis-data-commonType.md#assets)。如需同步多个资产,可将每个资产作为分布式对象的一个根属性实现。
105
106## 约束限制
107
108- 目前<!--RP2-->分布式数据对象只能在[跨端迁移](../application-models/hop-cross-device-migration.md)和[通过跨设备Call调用实现的多端协同](../application-models/hop-multi-device-collaboration.md#通过跨设备call调用实现多端协同)场景中使用。<!--RP2End-->
109
110- 不同设备间只有相同bundleName的应用才能直接同步。
111
112- 分布式数据对象的数据同步发生在同一个应用程序下,且同sessionID之间。
113
114- 不建议创建过多的分布式数据对象,每个分布式数据对象将占用100-150KB内存。
115
116- 每个分布式数据对象大小不超过500KB。
117
118- 设备A修改1KB数据,设备B收到变更通知,50ms内完成。
119
120- 单个应用程序最多只能创建16个分布式数据对象实例。
121
122- 考虑到性能和用户体验,最多不超过3个设备进行数据协同。
123
124- 如对复杂类型的数据进行修改,仅支持修改根属性,暂不支持下级属性修改。[资产同步机制](#资产同步机制)中,资产类型的数据支持下一级属性修改。
125
126- 支持JS接口间的互通,与其他语言不互通。
127
128## 接口说明
129
130以下是分布式对象跨设备数据同步功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[分布式数据对象](../reference/apis-arkdata/js-apis-data-distributedobject.md)。
131
132
133
134| 接口名称 | 描述 |
135| -------- | -------- |
136| create(context: Context, source: object): DataObject | 创建并得到一个分布式数据对象实例。 |
137| genSessionId(): string | 创建一个sessionId,可作为分布式数据对象的sessionId。 |
138| setSessionId(sessionId: string, callback: AsyncCallback&lt;void&gt;): void | 设置同步的sessionId,当可信组网中有多个设备时,多个设备间的对象如果设置为同一个sessionId,就能自动同步。 |
139| setSessionId(callback: AsyncCallback&lt;void&gt;): void | 退出所有已加入的session。 |
140| on(type: 'change', callback: (sessionId: string, fields: Array&lt;string&gt;) => void): void | 监听分布式数据对象的数据变更。 |
141| off(type: 'change', callback?: (sessionId: string, fields: Array&lt;string&gt;) => void): void | 取消监听分布式数据对象的数据变更。 |
142| on(type: 'status', callback: (sessionId: string, networkId: string, status: 'online' \| 'offline' ) => void): void | 监听分布式数据对象的上下线。 |
143| off(type: 'status', callback?: (sessionId: string, networkId: string, status: 'online' \|'offline' ) => void): void | 取消监听分布式数据对象的上下线。 |
144| save(deviceId: string, callback: AsyncCallback&lt;SaveSuccessResponse&gt;): void | 保存分布式数据对象。 |
145| revokeSave(callback: AsyncCallback&lt;RevokeSaveSuccessResponse&gt;): void | 撤回保存的分布式数据对象。 |
146| bindAssetStore(assetKey: string, bindInfo: BindInfo, callback: AsyncCallback&lt;void&gt;): void | 绑定融合资产。 |
147
148
149## 开发步骤
150
151### 在跨端迁移中使用分布式数据对象迁移数据
152
1531. 迁移发起端在onContinue接口中创建分布式数据对象并保存数据到接收端:
154
155    1.1 调用create接口创建并得到一个分布式数据对象实例。
156
157    1.2 调用genSessionId接口创建一个sessionId,调用setSessionId接口设置同步的sessionId,并将这个sessionId放入wantParam。
158
159    1.3 从wantParam获取接收端设备networkId,使用这个networkId调用save接口保存数据到接收端。
160
1612. 接收端在onCreate和onNewWant接口中创建分布式数据对象并注册恢复状态监听:
162
163    2.1 调用create接口创建并得到一个分布式数据对象实例。
164
165    2.2 注册恢复状态监听。收到状态为'restored'的回调通知时,表示接收端分布式数据对象已恢复发起端保存过来的数据。
166
167    2.3 从want.parameters中获取发起端放入的sessionId,调用setSessionId接口设置同步的sessionId。
168
169> **说明:**
170>
171> - 跨端迁移时,在迁移发起端调用setsessionId接口设置同步的sessionId后,必须再调用save接口保存数据到接收端。
172>
173<!--RP1-->
174> - 跨端迁移需要配置`continuable`标签,详见[跨端迁移开发步骤](../application-models/hop-cross-device-migration.md#开发步骤)。<!--RP1End-->
175>
176> - wantParam中的"sessionId"字段可能被其他服务占用,建议自定义一个key存取sessionId。
177>
178> - 可以使用资产类型记录资产附件(文件、图片、视频等类型文件)的相关信息,迁移资产类型数据时,对应的资产附件会一起迁移到对端。
179>
180> - 接收端需要将业务数据的初始值设置为undefined,才能恢复发起端保存的数据,否则接收端的数据会覆盖同步到发起端。如果是资产数据,需要将资产数据的各个属性设置为空字符串而不是将整个资产数据设置为undefined。
181>
182> - 暂不支持资产类型数组,如果要迁移多个文件,在业务数据中定义多条资产数据来记录。
183>
184> - 目前仅支持迁移分布式文件目录下的文件,非分布式文件目录下的文件可以复制或移动到分布式文件目录下再进行迁移。文件的操作和URI的获取详见[文件管理](../reference/apis-core-file-kit/js-apis-file-fs.md)和[文件URI](../reference/apis-core-file-kit/js-apis-file-fileuri.md)。
185
186```ts
187import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
188import { hilog } from '@kit.PerformanceAnalysisKit';
189import { window } from '@kit.ArkUI';
190import { commonType, distributedDataObject } from '@kit.ArkData';
191import { fileIo, fileUri } from '@kit.CoreFileKit';
192import { BusinessError } from '@kit.BasicServicesKit';
193
194// 业务数据定义
195class Data {
196  title: string | undefined;
197  text: string | undefined;
198  attachment: commonType.Asset; // 可以使用资产类型记录分布式目录下的文件,迁移资产数据时,对应的文件会一起迁移到接收端。(不迁移文件时不需要此字段,下方代码中的createAttachment、createEmptyAttachment方法也都不需要。)
199  // attachment2: commonType.Asset; // 暂不支持资产类型数组,如果要迁移多个文件,在业务数据中定义多条资产数据来记录
200
201  constructor(title: string | undefined, text: string | undefined, attachment: commonType.Asset) {
202    this.title = title;
203    this.text = text;
204    this.attachment = attachment;
205  }
206}
207
208const TAG = '[DistributedDataObject]';
209let dataObject: distributedDataObject.DataObject;
210
211export default class EntryAbility extends UIAbility {
212  // 1. 迁移发起端在onContinue接口中创建分布式数据对象并保存数据到接收端
213  onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult | Promise<AbilityConstant.OnContinueResult> {
214    // 1.1 调用create接口创建并得到一个分布式数据对象实例
215    let attachment = this.createAttachment();
216    let data = new Data('The title', 'The text', attachment);
217    dataObject = distributedDataObject.create(this.context, data);
218
219    // 1.2 调用genSessionId接口创建一个sessionId,调用setSessionId接口设置同步的sessionId,并将这个sessionId放入wantParam
220    let sessionId = distributedDataObject.genSessionId();
221    console.log(TAG + `gen sessionId: ${sessionId}`);
222    dataObject.setSessionId(sessionId);
223    wantParam.distributedSessionId = sessionId;
224
225    // 1.3 从wantParam获取接收端设备networkId,使用这个networkId调用save接口保存数据到接收端
226    let deviceId = wantParam.targetDevice as string;
227    console.log(TAG + `get deviceId: ${deviceId}`);
228    dataObject.save(deviceId);
229    return AbilityConstant.OnContinueResult.AGREE;
230  }
231
232  // 2. 接收端在onCreate和onNewWant接口中创建分布式数据对象并加入组网进行数据恢复
233  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
234    if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
235      if (want.parameters && want.parameters.distributedSessionId) {
236        this.restoreDistributedDataObject(want);
237      }
238    }
239  }
240
241  // 2. 接收端在onCreate和onNewWant接口中创建分布式数据对象并加入组网进行数据恢复
242  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
243    if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
244      if (want.parameters && want.parameters.distributedSessionId) {
245        this.restoreDistributedDataObject(want);
246      }
247    }
248  }
249
250  restoreDistributedDataObject(want: Want) {
251    if (!want.parameters || !want.parameters.distributedSessionId) {
252      console.error(TAG + 'missing sessionId');
253      return;
254    }
255
256    // 2.1 调用create接口创建并得到一个分布式数据对象实例
257    let attachment = this.createEmptyAttachment(); // 接收端需要将资产数据的各个属性设置为空字符串,才能恢复发起端保存的资产数据
258    let data = new Data(undefined, undefined, attachment);
259    dataObject = distributedDataObject.create(this.context, data);
260
261    // 2.2 注册恢复状态监听。收到状态为'restored'的回调通知时,表示接收端分布式数据对象已恢复发起端保存过来的数据(有资产数据时,对应的文件也迁移过来了)
262    dataObject.on('status', (sessionId: string, networkId: string, status: string) => {
263      if (status == 'restored') { // 收到'restored'的状态通知表示已恢复发起端保存的数据
264        console.log(TAG + `title: ${dataObject['title']}, text: ${dataObject['text']}`);
265      }
266    });
267
268    // 2.3 从want.parameters中获取发起端放入的sessionId,调用setSessionId接口设置同步的sessionId
269    let sessionId = want.parameters.distributedSessionId as string;
270    console.log(TAG + `get sessionId: ${sessionId}`);
271    dataObject.setSessionId(sessionId);
272  }
273
274  // 在分布式文件目录下创建一个文件并使用资产类型记录(也可以记录分布式文件目录下已有文件,非分布式文件目录下的文件可以复制或移动到分布式文件目录下再进行记录)
275  createAttachment() {
276    let attachment = this.createEmptyAttachment();
277    try {
278      let distributedDir: string = this.context.distributedFilesDir; // 分布式文件目录
279      let fileName: string = 'text_attachment.txt'; // 文件名
280      let filePath: string = distributedDir + '/' + fileName; // 文件路径
281      let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
282      fileIo.writeSync(file.fd, 'The text in attachment');
283      fileIo.closeSync(file.fd);
284      let uri: string = fileUri.getUriFromPath(filePath); // 获取文件URI
285      let stat = fileIo.statSync(filePath); // 获取文件详细属性信息
286
287      // 写入资产数据
288      attachment = {
289        name: fileName,
290        uri: uri,
291        path: filePath,
292        createTime: stat.ctime.toString(),
293        modifyTime: stat.mtime.toString(),
294        size: stat.size.toString()
295      }
296    } catch (e) {
297      let err = e as BusinessError;
298      console.error(TAG + `file error, error code: ${err.code}, error message: ${err.message}`);
299    }
300    return attachment;
301  }
302
303  createEmptyAttachment() {
304    let attachment: commonType.Asset = {
305      name: '',
306      uri: '',
307      path: '',
308      createTime: '',
309      modifyTime: '',
310      size: ''
311    }
312    return attachment;
313  }
314}
315```
316
317### 在多端协同中使用分布式数据对象
318
3191. 调用端调用startAbilityByCall接口拉起对端Ability:
320
321    1.1 调用genSessionId接口创建一个sessionId,通过分布式设备管理接口获取对端设备networkId。
322
323    1.2 组装want,并将sessionId放入want。
324
325    1.3 调用startAbilityByCall接口拉起对端Ability。
326
3272. 调用端拉起对端Ability后创建分布式数据对象并加入组网:
328
329   2.1 创建分布式数据对象实例。
330
331   2.2 注册数据变更监听。
332
333   2.3 设置同步sessionId加入组网。
334
3353. 被调用端被拉起后创建和恢复分布式数据对象:
336
337   3.1 创建分布式数据对象实例。
338
339   3.2 注册数据变更监听。
340
341   3.3 从want中获取源端放入的sessionId,使用这个sessionId加入组网。
342
343> **说明:**
344>
345> - 暂时只支持<!--RP3-->在[跨设备Call调用实现的多端协同](../application-models/hop-multi-device-collaboration.md#通过跨设备call调用实现多端协同)中使用分布式数据对象进行数据同步。<!--RP3End-->
346>
347> - 跨设备Call调用实现的多端协同开发<!--RP4-->需要申请`ohos.permission.DISTRIBUTED_DATASYNC`权限和配置单实例启动标签,详见跨设备Call调用实现的多端协同的[开发步骤](../application-models/hop-multi-device-collaboration.md#通过跨设备call调用实现多端协同)。<!--RP4End-->
348>
349> - wantParam中的"sessionId"字段可能被其他服务占用,建议自定义一个key存取sessionId。
350>
351> - 使用分布式设备管理获取对端设备networkId详见[设备信息查询开发指导](../distributedservice/devicemanager-guidelines.md#设备信息查询开发指导)。
352
353 示例代码如下:
354
355```ts
356import { AbilityConstant, Caller, common, UIAbility, Want } from '@kit.AbilityKit';
357import { hilog } from '@kit.PerformanceAnalysisKit';
358import { window } from '@kit.ArkUI';
359import { distributedDataObject } from '@kit.ArkData';
360import { distributedDeviceManager } from '@kit.DistributedServiceKit';
361import { BusinessError } from '@kit.BasicServicesKit';
362
363// 业务数据定义
364class Data {
365  title: string | undefined;
366  text: string | undefined;
367
368  constructor(title: string | undefined, text: string | undefined) {
369    this.title = title;
370    this.text = text;
371  }
372}
373
374const TAG = '[DistributedDataObject]';
375
376let sessionId: string;
377let caller: Caller;
378let dataObject: distributedDataObject.DataObject;
379
380export default class EntryAbility extends UIAbility {
381  // 1. 调用端调用startAbilityByCall接口拉起对端Ability
382  callRemote() {
383    if (caller) {
384      console.error(TAG + 'call remote already');
385      return;
386    }
387    let context = getContext(this) as common.UIAbilityContext;
388
389    // 1.1 调用genSessionId接口创建一个sessionId,通过分布式设备管理接口获取对端设备networkId
390    sessionId = distributedDataObject.genSessionId();
391    console.log(TAG + `gen sessionId: ${sessionId}`);
392    let deviceId = getRemoteDeviceId();
393    if (deviceId == "") {
394      console.warn(TAG + 'no remote device');
395      return;
396    }
397    console.log(TAG + `get remote deviceId: ${deviceId}`);
398
399    // 1.2 组装want,并将sessionId放入want
400    let want: Want = {
401      bundleName: 'com.example.collaboration',
402      abilityName: 'EntryAbility',
403      deviceId: deviceId,
404      parameters: {
405        'ohos.aafwk.param.callAbilityToForeground': true, // 前台启动,非必须
406        'distributedSessionId': sessionId
407      }
408    }
409    try {
410      // 1.3 调用startAbilityByCall接口拉起对端Ability
411      context.startAbilityByCall(want).then((res) => {
412        if (!res) {
413          console.error(TAG + 'startAbilityByCall failed');
414        }
415        caller = res;
416      })
417    } catch (e) {
418      let err = e as BusinessError;
419      console.error(TAG + `get remote deviceId error, error code: ${err.code}, error message: ${err.message}`);
420    }
421  }
422
423  // 2. 拉起对端Ability后创建分布式数据对象
424  createDataObject() {
425    if (!caller) {
426      console.error(TAG + 'call remote first');
427      return;
428    }
429    if (dataObject) {
430      console.error(TAG + 'create dataObject already');
431      return;
432    }
433    let context = getContext(this) as common.UIAbilityContext;
434
435    // 2.1 创建分布式数据对象实例
436    let data = new Data('The title', 'The text');
437    dataObject = distributedDataObject.create(context, data);
438
439    // 2.2 注册数据变更监听
440    dataObject.on('change', (sessionId: string, fields: Array<string>) => {
441      fields.forEach((field) => {
442        console.log(TAG + `${field}: ${dataObject[field]}`);
443      });
444    });
445    // 2.3 设置同步sessionId加入组网
446    dataObject.setSessionId(sessionId);
447  }
448
449  // 3. 被调用端被拉起后创建和恢复分布式数据对象
450  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
451    if (want.parameters && want.parameters.distributedSessionId) {
452      // 3.1 创建分布式数据对象实例
453      let data = new Data(undefined, undefined);
454      dataObject = distributedDataObject.create(this.context, data);
455
456      // 3.2 注册数据变更监听
457      dataObject.on('change', (sessionId: string, fields: Array<string>) => {
458        fields.forEach((field) => {
459          console.log(TAG + `${field}: ${dataObject[field]}`);
460        });
461      });
462      // 3.3 从want中获取源端放入的sessionId,使用这个sessionId加入组网
463      let sessionId = want.parameters.distributedSessionId as string;
464      console.log(TAG + `onCreate get sessionId: ${sessionId}`);
465      dataObject.setSessionId(sessionId);
466    }
467  }
468}
469
470// 获取可信组网中的设备
471function getRemoteDeviceId() {
472  let deviceId = "";
473  try {
474    let deviceManager = distributedDeviceManager.createDeviceManager('com.example.collaboration');
475    let devices = deviceManager.getAvailableDeviceListSync();
476    if (devices[0] && devices[0].networkId) {
477      deviceId = devices[0].networkId;
478    }
479  } catch (e) {
480    let err = e as BusinessError;
481    console.error(TAG + `get remote deviceId error, error code: ${err.code}, error message: ${err.message}`);
482  }
483  return deviceId;
484}
485```
486
487## 相关实例
488
489针对分布式数据对象开发,有以下相关实例可供参考:
490
491- [设备管理(ArkTS)(Full SDK)(API10)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/DistributedAppDev/DistributedAuthentication)
492
493- [分布式备忘录(ArkTS)(Full SDK)(API10)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SuperFeature/DistributedAppDev/DistributedNote)