鸿蒙跨设备协同开发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: string    age: number    isVis: boolean    constructor(name: string, age: number, isVis: boolean) {        this.name = name        this.age = age        this.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()接口中创建分布式数据对象并保存数据,执行流程如下:

    1. 在onContinue()接口中使用create()接口创建分布式数据对象,将所要迁移的数据填充到分布式数据对象数据中。

    2. 调用genSessionId()接口生成数据对象组网id,并使用该id调用setSessionId()加入组网,激活分布式数据对象。

    3. 使用save()接口将已激活的分布式数据对象持久化,确保源端退出后对端依然可以获取到数据。

    4. 将生成的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: string  father: string  constructor(mother: string, father: string) {    this.mother = mother    this.father = father  }}// 支持字符、数字、布尔值、对象的传递class SourceObject {  name: string | undefined  age: number | undefined  isVis: boolean | undefined  parent: ParentObject | undefined  constructor(name: string | undefined, age: number | undefined, isVis: boolean | undefined, parent: ParentObject | undefined) {    this.name = name    this.age = age    this.isVis = isVis    this.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) ?? '');    });  }}

注意

  • 分布式数据对象需要先激活,再持久化,因此必须在调用setSessionId()后再调用save()接口。

  • 对于源端迁移后需要退出的应用,为了防止数据未保存完成应用就退出,应采用await的方式等待save()接口执行完毕。【从API 12 起,onContinue()接口提供了async版本供该场景使用】

  • 当前,wantParams中“sessionId”字段在迁移流程中被系统占用,建议在wantParams中定义其他key值存储该分布式数据对象生成的id,避免数据异常

👉🏻 step 2:在对端UIAbility的onCreate()/onNewWant()中,通过加入与源端一致的分布式数据对象组网进行数据恢复。

  1. 创建空的分布式数据对象,用于接收恢复的数据;

  2. 从want中读取分布式数据对象组网id;

  3. 注册on()接口监听数据变更。在收到status为restore的事件的回调中,实现数据恢复完毕时需要进行的业务操作。

  4. 调用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);    // 读取分布式数据对象组网id    let 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);  }}

注意:

  • 对端加入组网的分布式数据对象不能为临时变量,因为在分布式数据对象on()接口为异步回调,可能在onCreate()/onNewWant()执行结束后才执行,临时变量被释放可能导致空指针异常。可以使用类成员变量避免该问题。

  • 对端用于创建分布式数据对象的Object,其属性应在激活分布式数据对象前置为undefined,否则会导致新数据加入组网后覆盖源端数据,数据恢复失败。

  • 应当在激活分布式数据对象之前,调用分布式数据对象的on()接口进行注册监听,防止错过restore事件导致数据恢复失败。

4、使用分布式数据对象接续文档/文件

对于图片、文档等文件类数据,需要先将其转换为资产commonType.Asset类型,再封装到分布式数据对象中进行迁移。迁移实现方式与普通的分布式数据对象类似,下面仅针对差异部分进行说明。

👉🏻 step 1:在源端,将需要迁移的文件资产保存到分布式数据对象DataObject中,执行流程如下:

  1. 将文件资产拷贝到分布式文件目录下,相关接口与用法详见基础文件接口。

  2. 使用分布式文件目录下的文件创建Asset资产对象。

  3. 将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: string  father: string  constructor(mother: string, father: string) {    this.mother = mother    this.father = father  }}class SourceObject {  name: string | undefined  age: number | undefined  isVis: boolean | undefined  parent: ParentObject | undefined  attachment: commonType.Asset | undefined  // 新增资产属性  constructor(name: string | undefined, age: number | undefined, isVis: boolean | undefined,    parent: ParentObject | undefined, attachment: commonType.Asset | undefined) {    this.name = name    this.age = age    this.isVis = isVis    this.parent = parent    this.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: '',    }    // 使用该空资产对象创建分布式数据对象,其余基础属性可以直接使用undefined    let 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资产对象,否则会导致资产同步失败。

如果我们想要同步多个资产,可采用两种方式实现:

  1. 可将每个资产作为分布式数据对象的一个根属性实现,适用于要迁移的资产数量固定的场景。

  2. 可以将资产数组传化为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 | undefined  assets: Object | undefined  // 分布式数据对象的中添加一个Object属性  constructor(name: string | undefined, assets: Object | undefined) {    this.name = name    this.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);    // ...}
### HarmonyOS 分布式协同办公应用开发考试 #### 考试资料准备 对于希望参加HarmonyOS分布式协同办公应用开发考试的考生来说,掌握全面而深入的知识体系至关重要。鸿蒙系统的设计理念不仅在于提供一个稳定的操作平台,更强调了跨设备的无缝连接与高效协作[^1]。 为了更好地理解这一核心价值,在备考过程中应重点关注以下几个方面: - **技术架构学习**:熟悉鸿蒙系统的分层结构和技术栈,特别是涉及分布式软总线技术和Stage模型的内容。这些知识点有助于开发者理解和实现不同终端的数据同步及服务调用机制。 - **工具链使用**:熟练操作DevEco Studio集成开发环境,能够独立完成从项目创建到最终发布的全流程工作。这包括但不限于安装配置IDE、编写代码逻辑以及调试运行程序等功能模块的学习和实践。 - **API 掌握程度**:深入了解HarmonyOS SDK所提供的各类接口函数及其应用场景,尤其是那些支持多端部署特性的部分。例如,如何借助特定API来实现在多种形态下的良好用户体验一致性等问题都需要加以重视。 #### 实战经验积累 除了理论知识外,实际动手能力和解决问题的经验同样不可或缺。建议考生参与一些小型项目的开发练习,如基于鸿蒙平台构建简单的聊天室应用程序或是日程管理类软件等。这类实践活动不仅能加深对所学概念的理解,还能有效提升解决复杂问题的能力。 此外,关注官方文档更新情况并积极参与社区交流也是获取最新资讯的好方法之一。通过与其他爱好者的互动讨论可以获得许多宝贵的意见和建议,从而帮助自己更快地成长进步。 #### 备考指南要点 针对即将来临的HarmonyOS分布式协同办公应用开发认证考试,以下是几点备考建议: - 制定合理的时表安排复习进度; - 注重基础知识的同时也要加强对新特性新技术的研究; - 尝试模拟真实场景下可能出现的各种挑战来进行针对性训练; - 积极寻求外部资源的支持,比如在线教程视频课程或者加入专门的兴趣小组等等; 最后提醒各位考生保持良好的心态面对即将到来的大考! ```python # 示例Python脚本用于展示如何查询传感器数据 import sensor_api as sa def get_sensor_data(sensor_id): try: data = sa.query_sensor(sensor_id) return f"Sensor {sensor_id} Data: {data}" except Exception as e: return str(e) print(get_sensor_data(1)) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值