1# 跨端迁移 2 3## 概述 4 5在用户使用设备的过程中,当使用情境发生变化时(例如从室内走到户外或者周围有更适合的设备等),之前使用的设备可能已经不适合继续当前的任务,此时,用户可以选择新的设备来继续当前的任务,原设备可按需决定是否退出任务,这个就是跨端迁移的场景。常见的跨端迁移场景实例:在平板上播放的视频,迁移到智慧屏继续播放,从而获得更佳的观看体验;平板上的视频应用退出。在应用开发层面,跨端迁移指在A端运行的UIAbility迁移到B端上,完成迁移后,B端UIAbility继续任务,而A端UIAbility可按需决定是否退出。 6 7跨端迁移的核心任务是将应用的当前状态(包括页面控件、状态变量等)无缝迁移到另一设备,从而在新设备上无缝接续应用体验。这意味着用户在一台设备上进行的操作可以在另一台设备的相同应用中快速切换并无缝衔接。 8 9主要功能包括: 10 11- 支持用户自定义数据存储及恢复。 12 13- 支持页面路由信息和页面控件状态数据的存储及恢复。 14 15- 支持应用兼容性检测。 16 17- 支持应用根据实际使用场景动态设置迁移状态(默认迁移状态为 **ACTIVE** 激活状态)。例如,编辑类应用在编辑文本的页面下才需要迁移,其他页面不需要迁移,则可以通过[`setMissionContinueState`](../reference/apis/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextsetmissioncontinuestate10)进行控制。 18 19- 支持应用动态选择是否进行页面栈恢复(默认进行页面栈信息恢复)。例如,应用希望自定义迁移到其他设备后显示的页面,则可以通过[`SUPPORT_CONTINUE_PAGE_STACK_KEY`](../reference/apis/js-apis-app-ability-wantConstant.md#wantconstantparams)进行控制。 20 21- 支持应用动态选择迁移成功后是否退出迁移源端应用(默认迁移成功后退出迁移源端应用)。可以通过[`SUPPORT_CONTINUE_SOURCE_EXIT_KEY`](../reference/apis/js-apis-app-ability-wantConstant.md#wantconstantparams)进行控制。 22 23 > **说明:** 24 > 25 > 开发者可以开发具有迁移能力的应用,迁移的触发由系统应用完成。 26 27 28## 运作机制 29 30 31 321. 在源端,通过`UIAbility`的[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityoncontinue)回调,开发者可以保存待接续的业务数据。例如,在浏览器应用中完成跨端迁移,开发者需要使用[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityoncontinue)回调保存页面URL等业务内容,而系统将自动保存页面状态,如当前浏览进度。 332. 分布式框架提供了跨设备应用界面、页面栈以及业务数据的保存和恢复机制,它负责将数据从源端发送到对端。 343. 在对端,同一`UIAbility`可以通过[`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)/[`onNewWant()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityonnewwant)接口来恢复业务数据。 35 36 37## 约束限制 38 39- 跨端迁移要求在同一`UIAbility`之间进行,也就是需要相同的`bundleName`、`abilityName`和签名信息。 40- 为了获得最佳体验,使用`wantParam`传输的数据需要控制在100KB以下。 41- 当前部分ArkUI组件支持迁移后,将特定状态恢复到对端设备。详情请见[分布式迁移标识](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-restoreId.md) 42 43## 开发步骤 44 451. 需要申请`ohos.permission.DISTRIBUTED_DATASYNC`权限,配置方式请参见[配置文件权限声明](../security/accesstoken-guidelines.md#配置文件权限声明)。 46 472. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/accesstoken-guidelines.md#向用户申请授权)。 48 493. 在[module.json5配置文件](../quick-start/module-configuration-file.md)的abilities标签中配置跨端迁移标签`continuable`。 50 51 ```json 52 { 53 "module": { 54 // ... 55 "abilities": [ 56 { 57 // ... 58 "continuable": true, // 配置UIAbility支持迁移 59 } 60 ] 61 } 62 } 63 ``` 64 65 > **说明:** 66 > 67 > 根据需要配置应用启动模式类型,配置详情请参照[UIAbility组件启动模式](uiability-launch-type.md)。 68 694. 在源端`UIAbility`中实现[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityoncontinue)回调。 70 71 当`UIAbility`实例触发迁移时,[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityoncontinue)回调在源端被调用,开发者可以在该接口中保存迁移数据,实现应用兼容性检测,决定是否支持此次迁移。 72 73 - 保存迁移数据:开发者可以将要迁移的数据通过键值对的方式保存在`wantParam`参数中。 74 - 应用兼容性检测:开发者可以通过从`wantParam`参数中获取对端应用的版本号与[源端应用版本号](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/faqs/faqs-bundle-management.md)做兼容性校验。开发者可以在触发迁移时从[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityoncontinue)回调中`wantParam.version`获取到迁移对端应用的版本号与迁移源端应用版本号做兼容校验。 75 - 迁移决策:开发者可以通过[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityoncontinue)回调的返回值决定是否支持此次迁移。 76 77 ```ts 78 import UIAbility from '@ohos.app.ability.UIAbility'; 79 import AbilityConstant from '@ohos.app.ability.AbilityConstant'; 80 81 export default class EntryAbility extends UIAbility { 82 onContinue(wantParam: Record<string, Object>):AbilityConstant.OnContinueResult { 83 let version = wantParam.version; 84 let targetDevice = wantParam.targetDevice; 85 console.info(`onContinue version = ${version}, targetDevice: ${targetDevice}`); // 准备迁移数据 86 87 // 获取源端版本号 88 let versionSrc: number = -1; // 请填充具体获取版本号的代码 89 90 // 兼容性校验 91 if (version !== versionSrc) { 92 // 在兼容性校验不通过时返回MISMATCH 93 return AbilityConstant.OnContinueResult.MISMATCH; 94 } 95 96 // 将要迁移的数据保存在wantParam的自定义字段(例如data)中 97 const continueInput = '迁移的数据'; 98 wantParam['data'] = continueInput; 99 100 return AbilityConstant.OnContinueResult.AGREE; 101 } 102 } 103 ``` 104 1055. 源端设备`UIAbility`实例在冷启动和热启动情况下分别会调用不同的接口来恢复数据和加载UI。 106 在对端设备的`UIAbility`中,需要实现[`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)/[`onNewWant()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityonnewwant)接口来恢复迁移数据。 107 108 通过在[`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)/[`onNewWant()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityonnewwant)回调中检查`launchReason`,可以判断此次启动是否有迁移触发。开发者可以从`want`中获取之前保存的迁移数据,并在数据恢复后调用`restoreWindowStage()`来触发页面恢复,包括页面栈信息。 109 110 ```ts 111 import UIAbility from '@ohos.app.ability.UIAbility'; 112 import AbilityConstant from '@ohos.app.ability.AbilityConstant'; 113 import Want from '@ohos.app.ability.Want'; 114 115 export default class EntryAbility extends UIAbility { 116 storage : LocalStorage = new LocalStorage(); 117 118 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 119 console.info('EntryAbility onCreate') 120 if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) { 121 // 将上述的保存的数据取出恢复 122 let continueInput = ''; 123 if (want.parameters != undefined) { 124 continueInput = JSON.stringify(want.parameters.data); 125 console.info(`continue input ${continueInput}`) 126 } 127 // 将数据显示当前页面 128 this.context.restoreWindowStage(this.storage); 129 } 130 } 131 132 onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { 133 console.info('EntryAbility onNewWant') 134 if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) { 135 // get user data from want params 136 let continueInput = ''; 137 if (want.parameters != undefined) { 138 continueInput = JSON.stringify(want.parameters.data); 139 console.info(`continue input ${continueInput}`); 140 } 141 this.context.restoreWindowStage(this.storage); 142 } 143 } 144 } 145 ``` 146 147## 可选配置迁移能力 148 149### 动态配置迁移能力 150 151从API version 10开始,提供了支持动态配置迁移能力的功能。即应用可以根据实际使用场景,在需要迁移时开启应用迁移能力;在业务不需要迁移时则可以关闭迁移能力。 152 153开发者可以通过调用[`setMissionContinueState()`](../reference/apis/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextsetmissioncontinuestate10)接口对迁移能力进行设置。默认状态下,应用的迁移能力为**ACTIVE**状态,即迁移能力开启,可以迁移。 154 155**设置迁移能力的时机** 156 157迁移能力的改变可以根据实际业务需求和代码实现,发生在应用生命周期的绝大多数时机。本文介绍常用的几种配置方式。 158 159在`UIAbility`的[`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)回调中调用接口,可以在应用创建时设置应用的迁移状态。 160 161```ts 162// EntryAbility.ets 163import UIAbility from '@ohos.app.ability.UIAbility'; 164import AbilityConstant from '@ohos.app.ability.AbilityConstant'; 165import Want from '@ohos.app.ability.Want'; 166 167export default class EntryAbility extends UIAbility { 168 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 169 // ... 170 this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) => { 171 console.info(`setMissionContinueState: ${JSON.stringify(result)}`); 172 }); 173 // ... 174 } 175} 176``` 177 178在页面的`onPageShow()`回调中调用接口,可以设置单个页面出现时应用的迁移状态。 179 180```ts 181// PageName.ets 182import AbilityConstant from '@ohos.app.ability.AbilityConstant'; 183import common from '@ohos.app.ability.common'; 184@Entry 185@Component 186struct PageName { 187 private context = getContext(this) as common.UIAbilityContext; 188 build() { 189 // ... 190 } 191 // ... 192 onPageShow(){ 193 // 进入该页面时,将应用设置为可迁移状态 194 this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => { 195 console.info(`setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`); 196 }); 197 } 198} 199``` 200 201在某个组件的触发事件中设置应用迁移能力。 202 203```ts 204// PageName.ets 205import AbilityConstant from '@ohos.app.ability.AbilityConstant'; 206import common from '@ohos.app.ability.common'; 207 208@Entry 209@Component 210struct PageName { 211 private context = getContext(this) as common.UIAbilityContext; 212 build() { 213 // ... 214 Button() { 215 // ... 216 }.onClick(()=>{ 217 // 点击该按钮时,将应用设置为可迁移状态 218 this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => { 219 console.info(`setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`); 220 }); 221 }) 222 } 223} 224``` 225 226**保证迁移连续性** 227 228由于迁移加载时,对端拉起的应用可能执行过自己的迁移状态设置命令(例如,冷启动时对端在[`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)中设置了 **INACTIVE** ;热启动时对端已打开了不可迁移的页面,迁移状态为 **INACTIVE** 等情况)。为了保证迁移过后的应用依然具有可以迁移回源端的能力,应在 [`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)/[`onNewWant()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityonnewwant)的迁移调用判断中,将迁移状态设置为 **ACTIVE** 。 229 230```ts 231// EntryAbility.ets 232import UIAbility from '@ohos.app.ability.UIAbility'; 233import AbilityConstant from '@ohos.app.ability.AbilityConstant'; 234import Want from '@ohos.app.ability.Want'; 235 236export default class EntryAbility extends UIAbility { 237 // ... 238 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 239 // ... 240 // 调用原因为迁移时,设置状态为可迁移,应对冷启动情况 241 this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) => { 242 console.info(`setMissionContinueState INACTIVE result: ${JSON.stringify(result)}`); 243 }); 244 } 245 246 onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { 247 // ... 248 // 调用原因为迁移时,设置状态为可迁移,应对热启动情况 249 if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) { 250 this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => { 251 console.info(`setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`); 252 }); 253 } 254 } 255 // ... 256} 257``` 258 259### 按需迁移页面栈 260 261支持应用动态选择是否进行页面栈恢复(默认进行页面栈信息恢复)。如果应用不想使用系统默认恢复的页面栈,则可以设置不进行页面栈迁移,而需要在`onWindowStageRestore()`设置迁移后进入的页面,参数定义见[SUPPORT_CONTINUE_PAGE_STACK_KEY](../reference/apis/js-apis-app-ability-wantConstant.md#wantconstantparams)。 262 263应用在源端的页面栈中存在Index和Second路由,而在对端恢复时不需要按照源端页面栈进行恢复,需要恢复到指定页面。 264 265例如,`UIAbility`迁移不需要自动迁移页面栈信息。 266 267```ts 268// EntryAbility.ets 269import UIAbility from '@ohos.app.ability.UIAbility'; 270import AbilityConstant from '@ohos.app.ability.AbilityConstant'; 271import wantConstant from '@ohos.app.ability.wantConstant'; 272import window from '@ohos.window'; 273 274export default class EntryAbility extends UIAbility { 275 // ... 276 277 onContinue(wantParam: Record<string, Object>) { 278 console.info(`onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`); 279 wantParam[wantConstant.Params.SUPPORT_CONTINUE_PAGE_STACK_KEY] = false; 280 return AbilityConstant.OnContinueResult.AGREE; 281 } 282 283 onWindowStageRestore(windowStage: window.WindowStage) { 284 // 若不需要自动迁移页面栈信息,则需要在此处设置应用迁移后进入的页面 285 windowStage.loadContent('pages/Index', (err, data) => { 286 if (err.code) { 287 return; 288 } 289 }); 290 } 291} 292``` 293 294### 按需退出 295 296支持应用动态选择迁移成功后是否退出迁移源端应用(默认迁移成功后退出迁移源端应用)。如果应用不想让系统自动退出迁移源端应用,则可以设置不退出,参数定义见[SUPPORT_CONTINUE_SOURCE_EXIT_KEY](../reference/apis/js-apis-app-ability-wantConstant.md#wantconstantparams)。 297 298示例:`UIAbility`设置迁移成功后,源端不需要退出迁移应用。 299 300```ts 301import UIAbility from '@ohos.app.ability.UIAbility'; 302import AbilityConstant from '@ohos.app.ability.AbilityConstant'; 303import wantConstant from '@ohos.app.ability.wantConstant'; 304 305export default class EntryAbility extends UIAbility { 306 // ... 307 308 onContinue(wantParam: Record<string, Object>) { 309 console.info(`onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`); 310 wantParam[wantConstant.Params.SUPPORT_CONTINUE_SOURCE_EXIT_KEY] = false; 311 return AbilityConstant.OnContinueResult.AGREE; 312 } 313} 314``` 315 316## 验证指导 317 318为方便开发者验证已开发的可迁移应用,当前OpenHarmony提供了一个全局任务中心demo作为迁移的入口。下面介绍通过安装全局任务中心来验证迁移的方式。 319 320### 1. 编译安装全局任务中心 321 322#### **配置环境** 323 324public-SDK不支持开发者使用所有的系统API,例如:全局任务中心使用的[**@ohos.distributedDeviceManager**](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-distributedDeviceManager.md)不包括在public_SDK中。因此为了正确编译安装全局任务中心,开发者需要替换full-SDK,具体操作可参见[替换指南](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/faqs/full-sdk-switch-guide.md)。 325 326> **说明**: 327> 328> 本文中的截图仅为参考,具体的显示界面请以实际使用的DevEco Studio和SDK的版本为准。 329 330#### **下载MissionCenter_Demo[示例代码](https://gitee.com/openharmony/ability_dmsfwk/tree/master/services/dtbschedmgr/test/missionCenterDemo/dmsDemo/entry/src/main)** 331 332#### **编译工程文件** 333 334 a.新建OpenHarmony 空的工程,找到对应的文件夹替换下载文件 335 336 337 b.自动签名,编译安装。 338 339 DevEco的自动签名模板默认签名权限为normal级。而本应用设计到ohos.permission.MANAGE_MISSIONS权限为system_core级别。自动生成的签名无法获得足够的权限,所以需要将权限升级为system_core级别,然后签名。 340 341 c.系统权限设置(以api10目录为例): 将Sdk目录下的openharmony\api版本(如:10)\toolchains\lib\UnsgnedReleasedProfileTemplate.json文件中的"apl":"normal_core"改为"apl":"system_core"。 342 3431. 点击file->Project Structure。 344  3452. 点击Signing Configs 点击OK。 346  3473. 连接开发板运行生成demo。 348 349### 2. 设备组网 350 3511. 打开A,B两设备的计算器。 3522. 点击右上角箭头选择B设备。 3533. 在B设备选择信任设备,弹出PIN码。 3544. 在A设备输入PIN码。 3555. 已组网成功,验证方法:在A设备输入数字,B设备同步出现则证明组网成功。 356 357### 3. 发起迁移 358 3591. 在B设备打开多设备协同权限的应用,A设备打开全局任务中心demo,A设备出现A设备名称(即:本机:OpenHarmony 3.2)和B设备名称(OpenHarmony 3.2)。 360  3612. 点击B设备名称,然后出现B设备的应用。 362  3633. 最后将应用拖拽到A设备名称处,A设备应用被拉起,B设备应用退出。 364  365