HarmonyOS Next开发学习手册——自由流转

844 篇文章 5 订阅
392 篇文章 0 订阅

场景介绍

随着全场景多设备的生活方式不断深入,用户拥有的设备越来越多,不同设备都能在适合的场景下提供良好的体验,例如手表可以提供及时的信息查看能力,电视可以带来沉浸的观影体验。但是,每个设备也有使用场景的局限,例如在电视上输入文本相对移动设备来说是非常糟糕的体验。当多个设备通过分布式操作系统能够相互感知、进而整合成一个超级终端时,设备与设备之间就可以取长补短、相互帮助,为用户提供更加自然流畅的分布式体验。

在HarmonyOS中,将跨多设备的分布式操作统称为流转;根据使用场景的不同,流转又分为跨端迁移和多端协同两种具体场景。

基本概念

  • 流转

在HarmonyOS中泛指跨多设备的分布式操作。流转能力打破设备界限,多设备联动,使用户应用程序可分可合、可流转,实现如邮件跨设备编辑、多设备协同健身、多屏游戏等分布式业务。流转为开发者提供更广的使用场景和更新的产品视角,强化产品优势,实现体验升级。流转按照使用场景可分为跨端迁移多端协同

  • 跨端迁移

在用户使用设备的过程中,当使用情境发生变化时(例如从室内走到户外或者周围有更合适的设备等),之前使用的设备可能已经不适合继续当前的任务,此时,用户可以选择新的设备来继续当前的任务,原设备可按需决定是否退出任务,这就是跨端迁移场景。

常见的跨端迁移场景实例:在平板上播放的视频,迁移到智慧屏继续播放,从而获得更佳的观看体验;平板上的视频应用退出。

在应用开发层面,跨端迁移指在A端运行的UIAbility迁移到B端上,完成迁移后,B端UIAbility继续任务,而A端UIAbility可按需决定是否退出。

  • 多端协同

用户拥有的多个设备,可以作为一个整体,为用户提供比单设备更加高效、沉浸的体验,这就是多端协同场景。

常见的多端协同场景实例:

场景一:两台设备A和B打开备忘录同一篇笔记进行双端协同编辑,在设备A上可以使用本地图库中的图片资源插入编辑,设备B上进行文字内容编辑。

场景二:设备A上正在和客户进行聊天,客户需要的资料在设备B上,可以通过聊天软件打开设备B上的文档应用选择到想要的资料回传到设备A上,然后通过聊天软件发送给客户。

在应用开发层面,多端协同指多端上的不同UIAbility/ServiceExtensionAbility同时运行、或者交替运行实现完整的业务;或者多端上的相同UIAbility/ServiceExtensionAbility同时运行实现完整的业务。

典型场景

  • 媒体播控:使用媒体播控,可以简单高效地将音频投放到其他HarmonyOS设备上播放,如在手机上播放的音频,可以投到2in1设备上继续播放。
  • 应用接续:指当用户在一个设备上操作某个应用时,可以在另一个设备的同一个应用中快速切换,并无缝衔接上一个设备的应用体验。
  • 跨设备拖拽:跨端拖拽提供跨设备的键鼠共享能力,支持在平板或2in1类型的任意两台设备之间拖拽文件、文本。
  • 跨设备剪贴板:当用户拥有多台设备时,可以通过跨设备剪贴板的功能,在A设备的应用上复制一段文本,粘贴到B设备的应用中,高效地完成多设备间的内容共享。

应用接续特性简介

应用接续,指当用户在一个设备上操作某个应用时,可以在另一个设备的同一个应用中快速切换,并无缝衔接上一个设备的应用体验。

比如在用户使用过程中,使用情景发生了变化,之前使用的设备不再适合继续当前任务,或者周围有更合适的设备,此时用户可以选择使用新的设备来继续当前的任务。接续完成后,之前设备的应用可退出或保留,用户可以将注意力集中在被拉起的设备上,继续执行任务。

如图所示,在手机上编辑备忘录,到办公室后切换到平板上继续编辑,完成任务的无缝衔接。

运作机制

  1. 在源端,通过UIAbility的onContinue()回调,开发者可以保存待接续的业务数据。

例如,浏览器应用完成应用接续,在源端浏览一个页面,到对端继续浏览。系统将自动保存页面状态,如当前页面的浏览进度;开发者需要通过onContinue接口保存页面url等业务内容。

  1. 分布式框架提供跨设备应用界面、页面栈以及业务数据的保存和恢复机制,负责将数据从源端发送到对端。
  2. 在对端,同一UIAbility通过onCreate/onNewWant接口恢复业务数据。

发起接续的场景

针对不同类型的应用,推荐的应用接续发起的界面及接续同步内容如下:

  • 浏览器:网页内容详情页,网页浏览进度同步
  • 备忘录:备忘录详情页,备忘浏览进度同步
  • 新闻:新闻详情页,新闻浏览进度同步
  • 阅读:小说阅读页,小说阅读进度同步
  • 视频:视频播放页,视频播放进度同步
  • 音乐:音乐播放页,歌单播放页,音乐播放进度同步
  • 会议:会议界面,当前会议同步
  • 邮件:新建邮件、回复转发邮件、阅读某封邮件界面,编辑内容及附件同步
  • 办公编辑:某条编辑页面,编辑内容同步
  • CAD:CAD编辑界面,编辑内容同步
  • 地图:路线查询、导航界面,当前路线及导航同步

应用接续开发指导

通过应用接续,可以实现将应用当前任务(包括页面控件状态变量等)迁移到目标设备,并在目标设备上接续使用。

可以实现的功能包括:

  • 存储及恢复自定义数据(应用业务内容)。
  • 存储及恢复页面路由信息和页面控件状态数据。
  • 应用兼容性检测。
  • 支持应用根据实际使用场景动态设置迁移状态(默认迁移状态为ACTIVE激活状态)。

如编辑类应用在编辑文本的页面下才需要迁移,其他页面不需要迁移,则可以通过 setMissionContinueState 进行控制。

  • 支持应用动态选择是否进行页面栈恢复(默认进行页面栈信息恢复)。

如应用希望自定义迁移到其他设备后显示的页面,则可以通过wantConstant.Params进行控制。

  • 支持应用动态选择流转成功后是否退出迁移源端应用(默认流转成功后退出迁移源端应用)。则可以通过@ohos.app.ability.wantConstant (wantConstant) 进行控制。

约束与限制

需同时满足以下条件,才能使用该功能:

  • 设备限制
    HarmonyOS NEXT Developer Preview0及以上版本的设备。
  • 使用限制
    • 双端设备需要登录同一华为帐号。
    • 双端设备需要打开Wi-Fi和蓝牙开关。
      条件允许时,建议双端设备接入同一个局域网,可提升数据传输的速度。
    • 应用接续只能在同应用(UIAbility)之间触发,双端设备都需要有该应用。
    • 为了接续体验,在onContinue回调中使用wantParam传输的数据需要控制在100KB以下,大数据量请使用分布式数据对象进行同步。

接口说明

以下为实现应用接续的主要接口,详细的接口说明可查阅参考文档

接口名描述
onContinue(wantParam : {[key: string]: Object}): OnContinueResult续源端在该回调中保存迁移所需要的数据,同时返回是否同意迁移:
- AGREE:表示同意。- REJECT:表示拒绝,如应用在onContinue中异常可以直接REJECT。- MISMATCH:表示版本不匹配,接续源端应用可以在onContinue中获取到迁移对端应用的版本号,进行协商后,如果版本不匹配导致无法迁移,可以返回该错误。
onCreate(want: Want, param: AbilityConstant.LaunchParam): void;接续目的端为冷启动或多实例应用热启动时,在该回调中完成数据恢复,并触发页面恢复。
onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void;接续目的端为单实例应用热启动时,在该回调中完成数据恢复,并触发页面恢复。

开发步骤

  1. 申请权限。

说明
* 如果仅使用want迁移数据,无需申请权限。
* 如果需要使用分布式数据对象迁移数据,需要申请权限。

  1. 数据迁移需要申请ohos.permission.DISTRIBUTED_DATASYNC权限,详见声明权限。

  2. 由于数据迁移使用权限需要用户授权,所以在应用首次启动时弹窗向用户申请授权,详见向用户申请授权。

  3. 启用应用接续能力。
    在module.json5文件的abilities中,将continuable标签配置为“true”,表示该UIAbility可被迁移。配置为false的UIAbility将被系统识别为无法迁移且该配置默认值为false。

{
  "module": {
    ...
    "abilities": [
      {
        ...
        "continuable": true,
      }
    ]
  }
}
  1. 根据需要配置应用启动模式类型,配置详情请参照UIAbility组件启动模式。
  2. 在源端UIAbility中实现onContinue()接口。

当应用触发迁移时,onContinue()接口在源端被调用,开发者可以在该接口中保存迁移数据,实现应用兼容性检测,决定是否支持此次迁移。

  • 保存迁移数据:开发者可以将要迁移的数据通过键值对的方式保存在wantParam中。
  • 应用兼容性检测:开发者可以从onContinue()入参wantParam.version获取到迁移对端应用的版本号,然后与迁移源端应用版本号做兼容校验。
  • 迁移决策:开发者可以通过onContinue接口的返回值决定是否支持此次迁移。

示例如下:

import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';

export default class EntryAbility extends UIAbility {
  onContinue(wantParam: Record<string, Object>) {
    let versionDst = wantParam.version;  // 获取迁移对端应用的版本号
    let versionSrc: number = 0; // 获取迁移源端即本端应用的版本号
    if (versionDst > versionSrc) { // 兼容性校验
       // 兼容性校验不满足
      return AbilityConstant.OnContinueResult.MISMATCH;
    }
    console.info(`onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`)
    // 迁移数据保存
    let continueInput = '迁移的数据';
    if (continueInput) {
      // 将要迁移的数据保存在wantParam的自定义字段(如:data)中;
      wantParam["data"] = continueInput;
    }
     return AbilityConstant.OnContinueResult.AGREE;
  }
}
  1. 在Stage模型中,应用在不同启动模式下将调用不同的接口,以恢复数据、加载界面。

不同情况下的函数调用如下图所示:

在目的端设UIAbility中实现onCreate()与onNewWant()接口,恢复迁移数据。
* onCreate实现示例
* 目的端设备上,在onCreate中根据launchReason判断该次启动是否为迁移LaunchReason.CONTINUATION。
* 开发者可以从want中获取保存的迁移数据。
* 若开发者使用系统页面栈恢复功能,则需要在onCreate()/onNewWant()执行完成前,调用restoreWindowStage(),来触发带有页面栈的页面恢复,如果不需要迁移页面栈可以参考按需迁移页面栈部分。

import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import Want from '@ohos.app.ability.Want';

export default class EntryAbility extends UIAbility {
  storage : LocalStorage = new LocalStorage();
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
      // 将上述的保存的数据取出恢复
      let continueInput = '';
      if (want.parameters != undefined) {
        continueInput = JSON.stringify(want.parameters.data);
        console.info(`continue input ${continueInput}`)
      }
      // 触发页面恢复
      this.context.restoreWindowStage(this.storage);
    }
  }
}

说明
接口restoreWindowStage(this.storage)必须在同步接口方法中执行,如果在异步回调中执行,可能会导致应用迁移后页面加载失败。

  • 如果是单实例应用,需要额外实现onNewWant()接口,实现方式与onCreate()的实现相同。

在onNewWant()中判断迁移场景,恢复数据,并触发页面恢复

import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import Want from '@ohos.app.ability.Want';

export default class EntryAbility extends UIAbility {
  storage : LocalStorage = new LocalStorage();
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    console.info(`EntryAbility onNewWant ${AbilityConstant.LaunchReason.CONTINUATION}`)
    if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
      // 将上述的保存的数据取出恢复
      let continueInput = '';
      if (want.parameters != undefined) {
        continueInput = JSON.stringify(want.parameters.data);
        console.info(`continue input ${continueInput}`);
      }
      this.context.restoreWindowStage(this.storage);
    }
  }
}

迁移功能可选配置

动态配置迁移能力

从API version 10起,提供了支持动态配置迁移能力的功能。即应用可以根据实际使用场景,在需要迁移功能时,设置开启应用迁移能力;在业务不需要迁移时,则可以关闭迁移能力。开发者可以通过调用setMissionContinueState接口对迁移能力进行设置。

接口状态值含义
AbilityConstant.ContinueState.ACTIVE应用当前可迁移能力开启
AbilityConstant.ContinueState.INACTIVE应用当前可迁移能力关闭

设置迁移能力的时机

默认状态下,应用的迁移能力为ACTIVE状态,即可以迁移。

如果需要实现某些特殊场景,比如只在具体某个页面下支持迁移,或者只在某个事件发生时才支持迁移,可以按照如下步骤进行配置。

  1. 在Ability的onCreate生命周期回调中,关闭迁移能力。
// EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import Want from '@ohos.app.ability.Want';
export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // ...
    this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) => {
      console.info(`setMissionContinueState: ${JSON.stringify(result)}`);
    });
    // ...
  }
}
  1. 如果需要在具体某个页面中打开迁移能力,可以在该页面的onPageShow()函数中设置。
// PageName.ets
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import common from '@ohos.app.ability.common'
@Entry
@Component
struct PageName {
  private context = getContext(this) as common.UIAbilityContext;
  build() {
    // ...
  }
  // ...
  onPageShow(){
  // 进入该页面时,将应用设置为可迁移状态
    this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
      console.info('setMissionContinueState ACTIVE result: ', JSON.stringify(result));
    });
  }
}
  1. 如果在某个组件的触发事件中打开迁移能力,可以在该事件中设置。下面以Button组件的onClick事件为例进行介绍。
// PageName.ets
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import common from '@ohos.app.ability.common'
@Entry
@Component
struct PageName {
  private context = getContext(this) as common.UIAbilityContext;
  build() {
    // ...
    Button() {
      //...
    }.onClick(()=>{
    //点击该按钮时,将应用设置为可迁移状态
      this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
        console.info('setMissionContinueState ACTIVE result: ', JSON.stringify(result));
      });
    })
  }
}

保证迁移连续性

由于迁移加载时,目标端拉起的应用可能执行过自己的迁移状态设置命令(如:冷启动时目标端在onCreate中设置了INACTIVE;热启动时对端已打开了不可迁移的页面,迁移状态为INACTIVE等情况)。为了保证迁移过后的应用依然具有可以迁移回源端的能力,应在onCreate和onNewWant的迁移调用判断中,将迁移状态设置为ACTIVE

// EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import Want from '@ohos.app.ability.Want';
export default class EntryAbility extends UIAbility {
  // ...
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // ...
    // 调用原因为迁移时,设置状态为不可迁移,应对冷启动情况
    this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) => {
      console.info(`setMissionContinueState: ${JSON.stringify(result)}`);
    });
  }
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // ...
    // 调用原因为迁移时,设置状态为可迁移,应对热启动情况
    if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
      this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
        console.info('setMissionContinueState ACTIVE result: ', JSON.stringify(result));
      });
    }
  }
  // ...
}

按需迁移页面栈

支持应用动态选择是否进行页面栈恢复(默认进行页面栈信息恢复)。如果应用不想使用系统默认恢复的页面栈,则可以设置不进行页面栈迁移,而需要在onWindowStageRestore设置迁移后进入的页面,参数定义见 SUPPORT_CONTINUE_PAGE_STACK_KEY 。

应用在源端的页面栈中存在Index和Second路由,而在目标端恢复时不需要按照源端页面栈进行恢复,需要恢复到指定页面。

示例:应用迁移不需要自动迁移页面栈信息

// EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import wantConstant from '@ohos.app.ability.wantConstant';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
  // ...
  onContinue(wantParam: Record<string, Object>) {
    console.info(`onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`);
    wantParam[wantConstant.Params.SUPPORT_CONTINUE_PAGE_STACK_KEY] = false;
    return AbilityConstant.OnContinueResult.AGREE;
  }
  // ...
  onWindowStageRestore(windowStage: window.WindowStage) {
      // 若不需要自动迁移页面栈信息,则需要在此处设置应用迁移后进入的页面
    windowStage.loadContent('pages/Index', (err, data) => {
      if (err.code) {
        console.info('Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      console.info('Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
    });
  }
}

按需退出

支持应用动态选择迁移成功后是否退出迁移源端应用(默认迁移成功后退出迁移源端应用)。如果应用不想让系统自动退出迁移源端应用,则可以设置不退出,参数定义见 SUPPORT_CONTINUE_SOURCE_EXIT_KEY 。

示例:应用迁移设置不需要迁移成功后退出迁移源端应用

import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import wantConstant from '@ohos.app.ability.wantConstant';
export default class EntryAbility extends UIAbility {
  // ...
  onContinue(wantParam: Record<string, Object>) {
    console.info(`onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`);
    wantParam[wantConstant.Params.SUPPORT_CONTINUE_SOURCE_EXIT_KEY] = false;
    return AbilityConstant.OnContinueResult.AGREE;
  }
  // ...
}

支持同应用中不同Ability跨端迁移

一般情况下,跨端迁移的双端是同Ability之间,但有些应用在不同设备类型下的同一个业务Ability名称不同(即异Ability),为了支持该场景下的两个Ability之间能够完成迁移,可以通过在module.json5文件的abilities标签中配置迁移类型continueType进行关联。 需要迁移的两个Ability的continueType字段取值必须保持一致,示例如下:

说明

  • continueType在本应用中要保证唯一,字符串以字母、数字和下划线组成,最大长度127个字节,不支持中文。
  • continueType标签类型为字符串数组,如果配置了多个字段,当前仅第一个字段会生效。
 // 设备A
   {
     "module": {
       // ...
       "abilities": [
         {
           // ...
           "name": "Ability-deviceA",
           "continueType": ['continueType1'], // continueType标签配置
         }
       ]
     }
   }

   // 设备B
   {
     "module": {
       // ...
       "abilities": [
         {
           // ...
           "name": "Ability-deviceB",
           "continueType": ['continueType1'], // 与设备A相同的continueType标签
         }
       ]
     }
   }

快速启动目标应用

默认情况下,发起迁移后不会立即拉起对端的目标应用,而是等待迁移数据从源端同步到对端后,才会拉起。为了发起迁移后能够立即拉起目标应用,做到及时响应,可以通过在continueType标签中添加“_ContinueQuickStart”后缀进行生效,这样待迁移数据从源端同步到对端后只恢复迁移数据即可,提升应用迁移体验。

{
  "module": {
    // ...
    "abilities": [
      {
        // ...
        "name": "EntryAbility"
        "continueType": ['EntryAbility_ContinueQuickStart'], // 如果已经配置了continueType标签,可以在该标签值后添加'_ContinueQuickStart'后缀;如果没有配置continueType标签,可以使用AbilityName + '_ContinueQuickStart'作为continueType标签实现快速拉起目标应用
      }
    ]
  }
}

使用分布式数据对象迁移数据

当需要迁移的数据较大(100KB以上)或需要迁移文件时,可以使用分布式数据对象。原理与接口说明详见分布式数据对象跨设备数据同步。

说明
自API 12起,由于直接使用跨设备文件访问实现文件的迁移,难以获取文件同步完成的时间。为了保证更高的成功率,文件的迁移不建议继续通过该方式实现,推荐使用分布式数据对象携带资产的方式。开发者此前通过跨设备文件访问实现的文件迁移依然生效。

基础数据迁移

使用分布式数据对象,与上述开发步骤类似,需要在源端onContinue()接口中进行数据保存,并在对端的onCreate()/onNewWant()接口中进行数据恢复。

  1. 在源端UIAbility的 onContinue() 接口中创建分布式数据对象并保存数据,执行流程如下:
    1. 在onContinue()接口中使用create()接口创建分布式数据对象,将所要迁移的数据填充到分布式数据对象数据中。
    2. 调用genSessionId()接口生成数据对象组网id,并使用该id调用setSessionId()加入组网,激活分布式数据对象。
    3. 使用save()接口将已激活的分布式数据对象持久化,确保源端退出后对端依然可以获取到数据。
    4. 将生成的sessionId通过want传递到对端,供对端激活同步使用。

说明

  • 分布式数据对象需要先激活,再持久化,因此必须在调用setSessionId()后再调用save()接口。
  • 对于源端迁移后需要退出的应用,为了防止数据未保存完成应用就退出,应采用await的方式等待save()接口执行完毕。从API 12 起,onContinue()接口提供了async版本供该场景使用。
  • 当前,wantParams中“sessionId”字段在迁移流程中被系统占用,建议开发者在wantParams中定义其他key值存储该分布式数据对象生成的id,避免数据异常。

示例代码如下:

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) ?? '');
    });
  }
}
  1. 在对端UIAbility的onCreate()/onNewWant()中,通过加入与源端一致的分布式数据对象组网进行数据恢复。
    1. 创建空的分布式数据对象,用于接收恢复的数据;
    2. 从want中读取分布式数据对象组网id;
    3. 注册on()接口监听数据变更。在收到status为restore的事件的回调中,实现数据恢复完毕时需要进行的业务操作。
    4. 调用setSessionId()加入组网,激活分布式数据对象。

说明

  • 对端加入组网的分布式数据对象不能为临时变量,因为在分布式数据对象on()接口为异步回调,可能在onCreate()/onNewWant()执行结束后才执行,临时变量被释放可能导致空指针异常。可以使用类成员变量避免该问题。
  • 对端用于创建分布式数据对象的Object,其属性应在激活分布式数据对象前置为undefined,否则会导致新数据加入组网后覆盖源端数据,数据恢复失败。
  • 应当在激活分布式数据对象之前,调用分布式数据对象的on()接口进行注册监听,防止错过restore事件导致数据恢复失败。

示例代码如下:

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);
  }
}

文件资产迁移

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

  • 在源端,将需要迁移的文件资产保存到分布式数据对象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持久化保存
    // ...
}
  • 对端需要先创建一个各属性为空的Asset资产对象作为分布式数据对象的根属性。在接收到on()接口status为restored的事件的回调时,表示包括资产在内的数据同步完成,可以像获取基本数据一样获取到源端的资产对象。

说明
对端创建分布式数据对象时,SourceObject对象中的资产不能直接使用undefined初始化,需要创建一个各属性为undefined的Asset资产对象,否则会导致资产同步失败。

示例代码如下:

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: undefined,
      uri: undefined,
      path: undefined,
      createTime: undefined,
      modifyTime: undefined,
      size: undefined,
    }

    // 使用该空资产对象创建分布式数据对象,其余基础属性可以直接使用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']));
        }
    });
    // ...
  }
}

若应用想要同步多个资产,可采用两种方式实现:

  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;
  }
}

// 该函数用于将资产数组转为Record
GetAssetsWapper(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++) {
    wapper[`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.GetAssetsWapper(assets);
    let source: SourceObject = new SourceObject("jack", assetsWrapper);
    this.d_object = distributedDataObject.create(this.context, source);

    // ...
}

鸿蒙全栈开发全新学习指南

之前总有很多小伙伴向我反馈说,不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以这里为大家准备了一份实用的鸿蒙(HarmonyOS NEXT)学习路线与学习文档用来跟着学习是非常有必要的。

针对一些列因素,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。

本路线共分为四个阶段

第一阶段:鸿蒙初中级开发必备技能

在这里插入图片描述

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值