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的对象数>=2,可以跨设备同步数据。若设备掉线或将sessionId置为空,分布式数据对象退化为本地数据对象。 27 28 29## 运作机制 30 31**图1** 分布式数据对象运作机制 32 33 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 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 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<void>): void | 设置同步的sessionId,当可信组网中有多个设备时,多个设备间的对象如果设置为同一个sessionId,就能自动同步。 | 139| setSessionId(callback: AsyncCallback<void>): void | 退出所有已加入的session。 | 140| on(type: 'change', callback: (sessionId: string, fields: Array<string>) => void): void | 监听分布式数据对象的数据变更。 | 141| off(type: 'change', callback?: (sessionId: string, fields: Array<string>) => 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<SaveSuccessResponse>): void | 保存分布式数据对象。 | 145| revokeSave(callback: AsyncCallback<RevokeSaveSuccessResponse>): void | 撤回保存的分布式数据对象。 | 146| bindAssetStore(assetKey: string, bindInfo: BindInfo, callback: AsyncCallback<void>): 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)