鸿蒙跨设备协同开发08——使用分布式数据对象接续应用
如果你也对鸿蒙开发感兴趣,加入“Harmony自习室”吧!扫描下方名片,关注公众号,公众号更新更快,同时也有更多学习资料和技术讨论群。

1、前 言
本文是基于鸿蒙跨设备协同开发07——动态控制应用接续的进一步讨论。
我们在鸿蒙跨设备协同开发06——应用接续中有提到:
-
为了接续体验,在onContinue回调中使用wantParam传输的数据需要控制在100KB以下(大数据量使用分布式数据对象进行同步)
当我们遇到需要迁移的数据大于100KB时,则需要分布式数据对象进行同步了。
需要注意:
如果是API11及之前版本,实现分布式数据对象同步功能时,需要申请 ohos.permission.DISTRIBUTED_DATASYNC 权限,如果是API 12及以上时,无需申请此权限。
2、分布式数据对象介绍
分布式数据对象提供管理基本数据对象的相关能力,包括创建、查询、删除、修改、订阅等;同时支持相同应用多设备间的分布式数据对象协同能力。
分布式数据对象由 @ohos.data.distributedDataObject 模块提供能力。导入模块代码如下:
import { distributedDataObject } from '@kit.ArkData';
核心的API有:
// 创建一个分布式数据对象create(context: Context, source: object): DataObject// 随机创建一个sessionIdgenSessionId(): string
创建一个分布式数据对象和随机创建一个sessionId示例如下:
// 创建一个分布式数据对象// 导入模块import { UIAbility } from '@kit.AbilityKit';import { BusinessError } from '@kit.BasicServicesKit';import { window } from '@kit.ArkUI';let g_object: distributedDataObject.DataObject|null = null;class SourceObject {name: stringage: numberisVis: booleanconstructor(name: string, age: number, isVis: boolean) {this.name = namethis.age = agethis.isVis = isVis}}class EntryAbility extends UIAbility {onWindowStageCreate(windowStage: window.WindowStage) {let source: SourceObject = new SourceObject("jack", 18, false);// 创建一个分布式数据对象g_object = distributedDataObject.create(this.context, source);}}// 创建一个随机sessionIdlet sessionId: string = distributedDataObject.genSessionId();
分布式数据对象的使用核心还是在于DataObject,DataObject表示一个分布式数据对象。它主要的API有:
// 设置sessionId,可信组网中有多个设备处于协同状态时,如果多个设备间的分布式对象设置为同一个sessionId,就能自动同步。// callback接口和Promise接口setSessionId(sessionId: string, callback: AsyncCallback<void>): voidsetSessionId(sessionId?: string): Promise<void>// 退出所有已加入的sessionsetSessionId(callback: AsyncCallback<void>): void// 监听/取消监听 分布式数据对象的数据变更。on(type: 'change', callback: (sessionId: string, fields: Array<string>) => void): voidoff(type: 'change', callback?: (sessionId: string, fields: Array<string>) => void): void// 监听/取消监听 分布式数据对象的上下线。on(type: 'status', callback: (sessionId: string, networkId: string, status: 'online' | 'offline' ) => void): voidoff(type: 'status', callback?:(sessionId: string, networkId: string, status: 'online' | 'offline') => void): void// 保存分布式数据对象(callback回调和Promise版本)/*对象数据保存成功后,当应用存在时不会释放对象数据,当应用退出后,重新进入应用时,恢复保存在设备上的数据。有以下几种情况时,保存的数据将会被释放:- 存储时间超过24小时。- 应用卸载。- 成功恢复数据之后。【deviceId为 local 时,表示存储在本地设备】*/save(deviceId: string, callback: AsyncCallback<SaveSuccessResponse>): voidsave(deviceId: string): Promise<SaveSuccessResponse>// 撤回保存的分布式数据对象(callback回调和Promise版本)// 如果对象保存在本地设备,那么将删除所有受信任设备上所保存的数据。// 如果对象保存在其他设备,那么将删除本地设备上的数据。revokeSave(callback: AsyncCallback<RevokeSaveSuccessResponse>): voidrevokeSave(): Promise<RevokeSaveSuccessResponse>/*绑定分布式对象中的单个资产与其对应的数据库信息,当前版本只支持分布式对象中的资产与关系型数据库的绑定。使用callback方式异步回调。当分布式对象中包含的资产和关系型数据库中包含的资产指向同一个实体资产文件,即两个资产的Uri相同时,就会存在冲突,我们把这种资产称为融合资产。如果需要分布式数据管理进行融合资产的冲突解决,需要先进行资产的绑定。当应用退出session后,绑定关系随之消失。*/bindAssetStore(assetKey: string, bindInfo: BindInfo, callback: AsyncCallback<void>): voidbindAssetStore(assetKey: string, bindInfo: BindInfo): Promise<void>
⭐️ 以保存数据为例,示例代码如下:
g_object.setSessionId("123456");g_object.save("local").then((result: distributedDataObject.SaveSuccessResponse) => {console.info("save callback");console.info("save sessionId " + result.sessionId);console.info("save version " + result.version);console.info("save deviceId " + result.deviceId);}).catch((err: BusinessError) => {console.info("save failed, error code = " + err.code);console.info("save failed, error message: " + err.message);});
⭐️ 以监听数据变化为例,示例代码如下:
// 删除数据变更回调changeCallbackg_object.off("change", (sessionId: string, fields: Array<string>) => {console.info("change" + sessionId);if (g_object != null && fields != null && fields != undefined) {for (let index: number = 0; index < fields.length; index++) {console.info("changed !" + fields[index] + " " + g_object[fields[index]]);}}});// 删除所有的数据变更回调g_object.off("change");
3、使用分布式数据对象接续基础数据
使用分布式数据对象接续与普通的应用接续相似(详见之前的文章鸿蒙跨设备协同开发06——应用接续),需要在源端onContinue()接口中进行数据保存,并在对端的onCreate()/onNewWant()接口中进行数据恢复。
👉🏻 step 1:在源端UIAbility的onContinue()接口中创建分布式数据对象并保存数据,执行流程如下:
-
-
在onContinue()接口中使用create()接口创建分布式数据对象,将所要迁移的数据填充到分布式数据对象数据中。
-
调用genSessionId()接口生成数据对象组网id,并使用该id调用setSessionId()加入组网,激活分布式数据对象。
-
使用save()接口将已激活的分布式数据对象持久化,确保源端退出后对端依然可以获取到数据。
-
将生成的sessionId通过want传递到对端,供对端激活同步使用。
-
示例代码如下:
import distributedDataObject from '@ohos.data.distributedDataObject';import UIAbility from '@ohos.app.ability.UIAbility';import { BusinessError } from '@ohos.base';const TAG: string = '[MigrationAbility]';const DOMAIN_NUMBER: number = 0xFF00;// 业务数据定义class ParentObject {mother: stringfather: stringconstructor(mother: string, father: string) {this.mother = motherthis.father = father}}// 支持字符、数字、布尔值、对象的传递class SourceObject {name: string | undefinedage: number | undefinedisVis: boolean | undefinedparent: ParentObject | undefinedconstructor(name: string | undefined, age: number | undefined, isVis: boolean | undefined, parent: ParentObject | undefined) {this.name = namethis.age = agethis.isVis = isVisthis.parent = parent}}export default class MigrationAbility extends UIAbility {d_object?: distributedDataObject.DataObject;async onContinue(wantParam: Record<string, Object>): Promise<AbilityConstant.OnContinueResult> {// ...let parentSource: ParentObject = new ParentObject('jack mom', 'jack Dad');let source: SourceObject = new SourceObject("jack", 18, false, parentSource);// 创建分布式数据对象this.d_object = distributedDataObject.create(this.context, source);// 生成数据对象组网id,激活分布式数据对象let dataSessionId: string = distributedDataObject.genSessionId();this.d_object.setSessionId(dataSessionId);// 将组网id存在want中传递到对端wantParam['dataSessionId'] = dataSessionId;// 数据对象持久化,确保迁移后即使应用退出,对端依然能够恢复数据对象// 从wantParam.targetDevice中获取到对端设备的networkId作为入参await this.d_object.save(wantParam.targetDevice as string).then((result:distributedDataObject.SaveSuccessResponse) => {hilog.info(DOMAIN_NUMBER, TAG, `Succeeded in saving. SessionId: ${result.sessionId},version:${result.version}, deviceId:${result.deviceId}`);}).catch((err: BusinessError) => {hilog.error(DOMAIN_NUMBER, TAG, 'Failed to save. Error: ', JSON.stringify(err) ?? '');});}}
| 注意
|
👉🏻 step 2:在对端UIAbility的onCreate()/onNewWant()中,通过加入与源端一致的分布式数据对象组网进行数据恢复。
-
创建空的分布式数据对象,用于接收恢复的数据;
-
从want中读取分布式数据对象组网id;
-
注册on()接口监听数据变更。在收到status为restore的事件的回调中,实现数据恢复完毕时需要进行的业务操作。
-
调用setSessionId()加入组网,激活分布式数据对象。
实例代码如下:
import AbilityConstant from '@ohos.app.ability.AbilityConstant';import UIAbility from '@ohos.app.ability.UIAbility';import type Want from '@ohos.app.ability.Want';const TAG: string = '[MigrationAbility]';const DOMAIN_NUMBER: number = 0xFF00;// 示例数据对象定义与上同export default class MigrationAbility extends UIAbility {d_object?: distributedDataObject.DataObject;onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {// ...// 调用封装好的分布式数据对象处理函数this.handleDistributedData(want);}}onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {if (want.parameters !== undefined) {// ...// 调用封装好的分布式数据对象处理函数this.handleDistributedData(want);}}}handleDistributedData(want: Want) {// 创建空的分布式数据对象let remoteSource: SourceObject = new SourceObject(undefined, undefined, undefined, undefined);this.d_object = distributedDataObject.create(this.context, remoteSource);// 读取分布式数据对象组网idlet dataSessionId = '';if (want.parameters !== undefined) {dataSessionId = want.parameters.dataSessionId as string;}// 添加数据变更监听this.d_object.on("status", (sessionId: string, networkId: string, status: 'online' | 'offline' | 'restored') => {hilog.info(DOMAIN_NUMBER, TAG, "status changed " + sessionId + " " + status + " " + networkId);if (status == 'restored') {if (this.d_object) {// 收到迁移恢复的状态时,可以从分布式数据对象中读取数据hilog.info(DOMAIN_NUMBER, TAG, "restored name:" + this.d_object['name']);hilog.info(DOMAIN_NUMBER, TAG, "restored parents:" + JSON.stringify(this.d_object['parent']));}}// 激活分布式数据对象this.d_object.setSessionId(dataSessionId);}}
| 注意:
|
4、使用分布式数据对象接续文档/文件
对于图片、文档等文件类数据,需要先将其转换为资产commonType.Asset类型,再封装到分布式数据对象中进行迁移。迁移实现方式与普通的分布式数据对象类似,下面仅针对差异部分进行说明。
👉🏻 step 1:在源端,将需要迁移的文件资产保存到分布式数据对象DataObject中,执行流程如下:
-
将文件资产拷贝到分布式文件目录下,相关接口与用法详见基础文件接口。
-
使用分布式文件目录下的文件创建Asset资产对象。
-
将Asset资产对象作为分布式数据对象的根属性保存。
随后,与普通数据对象的迁移的源端实现相同,可以使用该数据对象加入组网,并进行持久化保存。示例如下:
// 导入模块import distributedDataObject from '@ohos.data.distributedDataObject';import UIAbility from '@ohos.app.ability.UIAbility';import { BusinessError } from '@ohos.base';import window from '@ohos.window';import { fs, file } from '@ohos.file.fs';import commonType from '@ohos.data.commonType';const TAG: string = '[MigrationAbility]';const DOMAIN_NUMBER: number = 0xFF00;// 数据对象定义class ParentObject {mother: stringfather: stringconstructor(mother: string, father: string) {this.mother = motherthis.father = father}}class SourceObject {name: string | undefinedage: number | undefinedisVis: boolean | undefinedparent: ParentObject | undefinedattachment: commonType.Asset | undefined // 新增资产属性constructor(name: string | undefined, age: number | undefined, isVis: boolean | undefined,parent: ParentObject | undefined, attachment: commonType.Asset | undefined) {this.name = namethis.age = agethis.isVis = isVisthis.parent = parentthis.attachment = attachment;}}export default class MigrationAbility extends UIAbility {d_object?: distributedDataObject.DataObject;async onContinue(wantParam: Record<string, Object>): Promise<AbilityConstant.OnContinueResult> {// ...// 1. 将资产写入分布式文件目录下let distributedDir: string = this.context.distributedFilesDir; // 获取分布式文件目录路径let fileName: string = '/test.txt'; // 文件名let filePath: string = distributedDir + fileName; // 文件路径try {// 在分布式目录下创建文件let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);hilog.info(DOMAIN_NUMBER, TAG, 'Create file success.');// 向文件中写入内容(若资产为图片,可将图片转换为buffer后写入)fs.writeSync(file.fd, '[Sample] Insert file content here.');// 关闭文件fs.closeSync(file.fd);} catch (error) {let err: BusinessError = error as BusinessError;hilog.error(DOMAIN_NUMBER, TAG, `Failed to openSync / writeSync / closeSync. Code: ${err.code}, message: ${err.message}`);}// 2. 使用分布式文件目录下的文件创建资产对象let distributedUri: string = fileUri.getUriFromPath(filePath); // 获取分布式文件Uri// 获取文件参数let ctime: string = '';let mtime: string = '';let size: string = '';await file.stat(filePath).then((stat: file.Stat) => {ctime = stat.ctime.toString(); // 创建时间mtime = stat.mtime.toString(); // 修改时间size = stat.size.toString(); // 文件大小})// 创建资产对象let attachment: commonType.Asset = {name: fileName,uri: distributedUri,path: filePath,createTime: ctime,modifyTime: mtime,size: size,}// 3. 将资产对象作为分布式数据对象的根属性,创建分布式数据对象let parentSource: ParentObject = new ParentObject('jack mom', 'jack Dad');let source: SourceObject = new SourceObject("jack", 18, false, parentSource, attachment);this.d_object = distributedDataObject.create(this.context, source);// 生成组网id,激活分布式数据对象,save持久化保存// ...}
👉🏻 step 2:对端需要先创建一个各属性为空的Asset资产对象作为分布式数据对象的根属性。在接收到on()接口status为restored的事件的回调时,表示包括资产在内的数据同步完成,可以像获取基本数据一样获取到源端的资产对象。实例代码如下:
import UIAbility from '@ohos.app.ability.UIAbility';import type Want from '@ohos.app.ability.Want';const TAG: string = '[MigrationAbility]';const DOMAIN_NUMBER: number = 0xFF00;export default class MigrationAbility extends UIAbility {d_object?: distributedDataObject.DataObject;handleDistributedData(want: Want) {// ...// 创建一个各属性为空的资产对象let attachment: commonType.Asset = {name: '',uri: '',path: '',createTime: '',modifyTime: '',size: '',}// 使用该空资产对象创建分布式数据对象,其余基础属性可以直接使用undefinedlet source: SourceObject = new SourceObject(undefined, undefined, undefined, undefined, attachment);this.d_object = distributedDataObject.create(this.context, source);this.d_object.on("status", (sessionId: string, networkId: string, status: 'online' | 'offline' | 'restored') => {if (status == 'restored') {// 收到监听的restored回调,表示分布式资产对象同步完成hilog.info(DOMAIN_NUMBER, TAG, "restored attachment:" + JSON.stringify(this.d_object['attachment']));}});// ...}}
| 注意: 对端创建分布式数据对象时,SourceObject对象中的资产不能直接使用undefined初始化,需要创建一个各属性为空的Asset资产对象,否则会导致资产同步失败。 |
如果我们想要同步多个资产,可采用两种方式实现:
-
可将每个资产作为分布式数据对象的一个根属性实现,适用于要迁移的资产数量固定的场景。
-
可以将资产数组传化为Object传递,适用于需要迁移的资产个数会动态变化的场景(如用户选择了不定数量的图片)。当前不支持直接将资产数组作为根属性传递。
其中方式1的实现可以直接参照添加一个资产的方式添加更多资产。方式2的示例如下所示:
// 导入模块import distributedDataObject from '@ohos.data.distributedDataObject';import UIAbility from '@ohos.app.ability.UIAbility';import commonType from '@ohos.data.commonType';// 数据对象定义class SourceObject {name: string | undefinedassets: Object | undefined // 分布式数据对象的中添加一个Object属性constructor(name: string | undefined, assets: Object | undefined) {this.name = namethis.assets = assets;}}// 该函数用于将资产数组转为RecordGetAssetsWrapper(assets: commonType.Assets): Record<string, commonType.Asset> {let wrapper: Record<string, commonType.Asset> = {}let num: number = assets.length;for (let i: number = 0; i < num; i++) {wrapper[`asset${i}`] = assets[i];}return wrapper;}export default class MigrationAbility extends UIAbility {d_object?: distributedDataObject.DataObject;async onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {// ...// 创建了多个资产对象let attachment1: commonType.Asset = {// ...}let attachment2: commonType.Asset = {// ...}// 将资产对象插入资产数组let assets: commonType.Assets = [];assets.push(attachment1);assets.push(attachment2);// 将资产数组转为Record Object,并用于创建分布式数据对象let assetsWrapper: Object = this.GetAssetsWrapper(assets);let source: SourceObject = new SourceObject("jack", assetsWrapper);this.d_object = distributedDataObject.create(this.context, source);// ...}
更多推荐



所有评论(0)