1# 跨端迁移 2 3## 概述 4 5在用户使用设备的过程中,当使用情境发生变化时(例如从室内走到户外或者周围有更适合的设备等),之前使用的设备可能已经不适合继续当前的任务,此时,用户可以选择新的设备来继续当前的任务,原设备可按需决定是否退出任务,这个就是跨端迁移的场景。常见的跨端迁移场景实例:在平板上播放的视频,迁移到智慧屏继续播放,从而获得更佳的观看体验;平板上的视频应用退出。在应用开发层面,跨端迁移指在A端运行的[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)迁移到B端上,完成迁移后,B端UIAbility继续任务,而A端UIAbility可按需决定是否退出。 6 7主要功能包括: 8 9- 支持用户自定义数据存储及恢复。 10 11- 支持页面路由信息和页面控件状态数据的存储及恢复。 12 13- 支持应用兼容性检测。 14 15- 支持应用根据实际使用场景动态设置迁移状态(默认迁移状态为 **ACTIVE** 激活状态)。例如,编辑类应用在编辑文本的页面下才需要迁移,其他页面不需要迁移,则可以通过[setMissionContinueState](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#setmissioncontinuestate10)进行控制。 16 17- 支持应用动态选择是否进行页面栈恢复(默认进行页面栈信息恢复)。例如,应用希望自定义迁移到其他设备后显示的页面,则可以通过[SUPPORT_CONTINUE_PAGE_STACK_KEY](../reference/apis-ability-kit/js-apis-app-ability-wantConstant.md#params)进行控制。 18 19- 支持应用动态选择迁移成功后是否退出迁移源端应用(默认迁移成功后退出迁移源端应用)。可以通过[SUPPORT_CONTINUE_SOURCE_EXIT_KEY](../reference/apis-ability-kit/js-apis-app-ability-wantConstant.md#params)进行控制。 20 21 > **说明:** 22 > 23 > 开发者可以开发具有迁移能力的应用,迁移的触发由系统应用完成。 24 25## 运作机制 26 27 28 291. 在源端,通过[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)的[onContinue()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#oncontinue)回调,开发者可以保存待迁移的业务数据。例如,在浏览器应用中完成跨端迁移,开发者需要使用onContinue()回调保存页面URL等业务内容。 302. 分布式框架提供了跨设备应用页面栈以及业务数据的保存和恢复机制,它负责将数据从源端发送到对端。 313. 在对端,同一UIAbility可以通过[onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#oncreate)(冷启动)和[onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onnewwant)(热启动)接口来恢复业务数据。 32 33## 跨端迁移流程 34 35以从对端的迁移入口发起迁移为例,跨端迁移流程如下图所示。 36 37 38 39## 约束限制 40 41为了获得最佳体验,使用`wantParam`传输的数据需要控制在100KB以下。 42 43## 开发步骤 44 451. 在[module.json5配置文件](../quick-start/module-configuration-file.md)的abilities标签中配置跨端迁移标签`continuable`。 46 47 ```json 48 { 49 "module": { 50 // ... 51 "abilities": [ 52 { 53 // ... 54 "continuable": true, // 配置UIAbility支持迁移 55 } 56 ] 57 } 58 } 59 ``` 60 61 > **说明:** 62 > 63 > 根据需要配置应用启动模式类型,配置详情请参照[UIAbility组件启动模式](uiability-launch-type.md)。 64 652. 在源端UIAbility中实现onContinue()回调。 66 67 当[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)实例触发迁移时,[onContinue()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#oncontinue)回调在源端被调用,开发者可以在该接口中通过同步或异步的方式来保存迁移数据,实现应用兼容性检测,决定是否支持此次迁移。 68 69 1. 保存迁移数据:开发者可以将要迁移的数据通过键值对的方式保存在`wantParam`参数中。 70 71 2. (可选)检测应用兼容性:开发者可以在触发迁移时从`onContinue()`入参`wantParam.version`获取到迁移对端应用的版本号,与迁移源端应用版本号做兼容校验。应用在校验版本兼容性失败后,需要提示用户迁移失败的原因。 72 73 > **说明:** 74 > 75 > 如果迁移过程中的兼容性问题对于应用迁移体验影响较小或无影响,可以跳过该步骤。 76 77 3. 返回迁移结果:开发者可以通过`onContinue()`回调的返回值决定是否支持此次迁移,接口返回值详见[AbilityConstant.OnContinueResult](../reference/apis-ability-kit/js-apis-app-ability-abilityConstant.md#oncontinueresult)。 78 79 80 `onContinue()`接口传入的`wantParam`参数中,有部分字段由系统预置,开发者可以使用这些字段用于业务处理。同时,应用在保存自己的`wantParam`参数时,也应注意不要使用同样的key值,避免被系统覆盖导致数据获取异常。详见下表: 81 82 | 字段|含义| 83 | ---- | ---- | 84 | version | 对端应用的版本号 | 85 | targetDevice | 对端设备的networkId | 86 87 ```ts 88 import { AbilityConstant, UIAbility } from '@kit.AbilityKit'; 89 import { hilog } from '@kit.PerformanceAnalysisKit'; 90 import { promptAction } from '@kit.ArkUI'; 91 92 const TAG: string = '[MigrationAbility]'; 93 const DOMAIN_NUMBER: number = 0xFF00; 94 95 export default class MigrationAbility extends UIAbility { 96 // 在onContinue中准备迁移数据 97 onContinue(wantParam: Record<string, Object>):AbilityConstant.OnContinueResult { 98 let targetVersion = wantParam.version; 99 let targetDevice = wantParam.targetDevice; 100 hilog.info(DOMAIN_NUMBER, TAG, `onContinue version = ${targetVersion}, targetDevice: ${targetDevice}`); 101 102 // 应用可根据源端版本号设置支持接续的最小兼容版本号,源端版本号可从app.json5文件中的versionCode字段获取;防止目标端版本号过低导致不兼容。 103 let versionThreshold: number = -1; // 替换为应用自己支持兼容的最小版本号 104 // 兼容性校验 105 if (targetVersion < versionThreshold) { 106 // 建议在校验版本兼容性失败后,提示用户拒绝迁移的原因 107 promptAction.openToast({ 108 message: '目标端应用版本号过低,不支持接续,请您升级应用版本后再试', 109 duration: 2000 110 }) 111 // 在兼容性校验不通过时返回MISMATCH 112 return AbilityConstant.OnContinueResult.MISMATCH; 113 } 114 115 // 将要迁移的数据保存在wantParam的自定义字段(例如data)中 116 const continueInput = '迁移的数据'; 117 wantParam['data'] = continueInput; 118 119 return AbilityConstant.OnContinueResult.AGREE; 120 } 121 } 122 ``` 123 1243. 对端设备的UIAbility通过实现[onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#oncreate)/[onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onnewwant)接口,来恢复迁移数据和加载UI。 125 126 不同的启动方式下会调用不同的接口,详见下图。 127 128  129 130 > **说明:** 131 > 1. 在应用迁移启动时,无论是冷启动还是热启动,都会在执行完onCreate()/onNewWant()后,触发[onWindowStageRestore()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onwindowstagerestore)生命周期函数,不执行[onWindowStageCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onwindowstagecreate)生命周期函数。 132 > 2. 开发者如果在`onWindowStageCreate()`中进行了一些应用启动时必要的初始化,那么迁移后需要在`onWindowStageRestore()`中执行同样的初始化操作,避免应用异常。 133 134 - 通过在onCreate()/onNewWant()回调中检查`launchReason`(CONTINUATION),可以判断此次启动是否由迁移触发。 135 - 开发者可以从[want](../reference/apis-ability-kit/js-apis-app-ability-want.md)中获取之前保存的迁移数据。 136 - 若开发者使用系统页面栈恢复功能,则需要在onCreate()/onNewWant()执行完成前,同步调用[restoreWindowStage()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#restorewindowstage),来触发带有页面栈的页面恢复,详见[按需迁移页面栈](./hop-cross-device-migration.md#按需迁移页面栈)。 137 138 ```ts 139 import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 140 import { hilog } from '@kit.PerformanceAnalysisKit'; 141 142 const TAG: string = '[MigrationAbility]'; 143 const DOMAIN_NUMBER: number = 0xFF00; 144 145 export default class MigrationAbility extends UIAbility { 146 storage : LocalStorage = new LocalStorage(); 147 148 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 149 hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Ability onCreate'); 150 if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) { 151 // 将上述保存的数据从want.parameters中取出恢复 152 let continueInput = ''; 153 if (want.parameters !== undefined) { 154 continueInput = JSON.stringify(want.parameters.data); 155 hilog.info(DOMAIN_NUMBER, TAG, `continue input ${JSON.stringify(continueInput)}`); 156 } 157 // 触发页面恢复 158 this.context.restoreWindowStage(this.storage); 159 } 160 } 161 162 onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { 163 hilog.info(DOMAIN_NUMBER, TAG, 'onNewWant'); 164 if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) { 165 // 将上述保存的数据从want.parameters中取出恢复 166 let continueInput = ''; 167 if (want.parameters !== undefined) { 168 continueInput = JSON.stringify(want.parameters.data); 169 hilog.info(DOMAIN_NUMBER, TAG, `continue input ${JSON.stringify(continueInput)}`); 170 } 171 // 触发页面恢复 172 this.context.restoreWindowStage(this.storage); 173 } 174 } 175 } 176 ``` 177 178## 可选配置迁移能力 179 180### 动态配置迁移能力 181 182从API version 10开始,提供了支持动态配置迁移能力的功能。即应用可以根据实际使用场景,在需要迁移时开启应用迁移能力;在业务不需要迁移时则可以关闭迁移能力。 183 184开发者可以通过调用[setMissionContinueState()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#setmissioncontinuestate10)接口对迁移能力进行设置。默认状态下,应用的迁移能力为**ACTIVE**状态,即迁移能力开启,可以迁移。 185 186**设置迁移能力的时机** 187 188如果需要实现某些特殊场景,比如只在具体某个页面下支持迁移,或只在某个事件发生时才支持迁移,可以按照如下步骤进行配置。 189 1901. 在[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)的[onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#oncreate)生命周期回调中,关闭迁移能力。 191 192 ```ts 193 // MigrationAbility.ets 194 import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 195 import { hilog } from '@kit.PerformanceAnalysisKit'; 196 197 const TAG: string = '[MigrationAbility]'; 198 const DOMAIN_NUMBER: number = 0xFF00; 199 200 export default class MigrationAbility extends UIAbility { 201 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 202 // ... 203 this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) => { 204 hilog.info(DOMAIN_NUMBER, TAG, `setMissionContinueState INACTIVE result: ${JSON.stringify(result)}`); 205 }); 206 // ... 207 } 208 } 209 ``` 210 211 2. 如果需要在具体某个页面中打开迁移能力,可以在页面的[onPageShow()](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#onpageshow)函数中调用接口。 212 213 ```ts 214 // Page_MigrationAbilityFirst.ets 215 import { AbilityConstant, common } from '@kit.AbilityKit'; 216 import { hilog } from '@kit.PerformanceAnalysisKit'; 217 218 const TAG: string = '[MigrationAbility]'; 219 const DOMAIN_NUMBER: number = 0xFF00; 220 221 @Entry 222 @Component 223 struct Page_MigrationAbilityFirst { 224 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 225 226 // ... 227 onPageShow(){ 228 // 进入该页面时,将应用设置为可迁移状态 229 this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => { 230 hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`); 231 }); 232 } 233 234 build() { 235 // ... 236 } 237 } 238 ``` 239 2403. 如果想要在某个组件的触发事件打开迁移能力,可以在该事件中调用。以Button组件的[onClick()](../reference/apis-arkui/arkui-ts/ts-universal-events-click.md#onclick)事件为例: 241 242 ```ts 243 // Page_MigrationAbilityFirst.ets 244 import { AbilityConstant, common } from '@kit.AbilityKit'; 245 import { hilog } from '@kit.PerformanceAnalysisKit'; 246 import { promptAction } from '@kit.ArkUI'; 247 248 const TAG: string = '[MigrationAbility]'; 249 const DOMAIN_NUMBER: number = 0xFF00; 250 251 @Entry 252 @Component 253 struct Page_MigrationAbilityFirst { 254 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 255 build() { 256 Column() { 257 //... 258 List({ initialIndex: 0 }) { 259 ListItem() { 260 Row() { 261 //... 262 } 263 .onClick(() => { 264 // 点击该按钮时,将应用设置为可迁移状态 265 this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => { 266 hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`); 267 promptAction.openToast({ 268 message: 'Success' 269 }); 270 }); 271 }) 272 } 273 //... 274 } 275 //... 276 } 277 //... 278 } 279 } 280 ``` 281 282### 按需迁移页面栈 283 284 285> **说明:** 286> 287> 1. 当前仅支持router路由的页面栈自动恢复,暂不支持navigation路由的页面栈自动恢复。 288> 2. 若应用使用navigation路由,可以设置不进行默认页面栈迁移(配置[SUPPORT_CONTINUE_PAGE_STACK_KEY](../reference/apis-ability-kit/js-apis-app-ability-wantConstant.md#params)参数为`false`),并将需要迁移的页面(或页面栈)信息保存在want中传递,然后在目标端手动加载指定页面。 289 290[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)的迁移默认恢复页面栈。开发者需要在[onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#oncreate)/[onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onnewwant)执行完成前,调用[restoreWindowStage()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#restorewindowstage),向系统传入当前的窗口上下文,用于页面栈的加载恢复。`restoreWindowStage()`接口必须在同步方法中执行。如果在异步回调中执行该接口,会导致在应用拉起时页面有概率加载失败。 291 292以`onCreate()`为例: 293 294```ts 295import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 296 297export default class MigrationAbility extends UIAbility { 298 storage : LocalStorage = new LocalStorage(); 299 300 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 301 // ... 302 // 同步执行结束前触发页面恢复 303 this.context.restoreWindowStage(this.storage); 304 } 305} 306``` 307 308如果应用不想通过系统自动恢复页面栈,可以通过配置[SUPPORT_CONTINUE_PAGE_STACK_KEY](../reference/apis-ability-kit/js-apis-app-ability-wantConstant.md#params)参数为`false`关闭该功能。开发者需要在[onWindowStageRestore()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onwindowstagerestore)中,指定迁移后进入的页面。不指定迁移后进入的页面,会导致迁移拉起后显示空白页面。 309 310例如,`UIAbility`在对端恢复时不需要按照源端页面栈进行恢复,而是需要恢复到指定页面`Page_MigrationAbilityThird`。 311 312```ts 313// MigrationAbility.ets 314import { AbilityConstant, UIAbility, wantConstant } from '@kit.AbilityKit'; 315import { hilog } from '@kit.PerformanceAnalysisKit'; 316import { window } from '@kit.ArkUI'; 317 318const TAG: string = '[MigrationAbility]'; 319const DOMAIN_NUMBER: number = 0xFF00; 320 321export default class MigrationAbility extends UIAbility { 322 onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult { 323 // ... 324 // 配置不使用系统页面栈恢复 325 wantParam[wantConstant.Params.SUPPORT_CONTINUE_PAGE_STACK_KEY] = false; 326 return AbilityConstant.OnContinueResult.AGREE; 327 } 328 329 onWindowStageRestore(windowStage: window.WindowStage): void { 330 // 不使用系统页面栈恢复时,需要在此处指定应用迁移后进入的页面 331 windowStage.loadContent('pages/page_migrationability/Page_MigrationAbilityThird', (err, data) => { 332 if (err.code) { 333 hilog.error(DOMAIN_NUMBER, TAG, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); 334 return; 335 } 336 }); 337 } 338} 339``` 340 341### 按需退出 342 343[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)的迁移默认迁移结束后退出源端应用。如果应用希望迁移后源端应用继续运行,或需要进行其他操作(如,保存草稿、清理资源等)后再自行触发退出,不想在迁移后立即自动退出源端应用,可以通过配置[SUPPORT_CONTINUE_SOURCE_EXIT_KEY](../reference/apis-ability-kit/js-apis-app-ability-wantConstant.md#params)参数为`false`设置源端迁移后不退出。 344 345示例:`UIAbility`设置迁移成功后,源端不需要退出迁移应用。 346 347```ts 348import { AbilityConstant, UIAbility, wantConstant } from '@kit.AbilityKit'; 349import { hilog } from '@kit.PerformanceAnalysisKit'; 350 351const TAG: string = '[MigrationAbility]'; 352const DOMAIN_NUMBER: number = 0xFF00; 353 354export default class MigrationAbility extends UIAbility { 355 // ... 356 onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult { 357 hilog.info(DOMAIN_NUMBER, TAG, `onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`); 358 wantParam[wantConstant.Params.SUPPORT_CONTINUE_SOURCE_EXIT_KEY] = false; 359 return AbilityConstant.OnContinueResult.AGREE; 360 } 361} 362``` 363 364### 支持同应用中不同Ability跨端迁移 365一般情况下,跨端迁移的双端是同Ability之间,但有些应用在不同设备类型下的同一个业务Ability名称不同(即异Ability),为了支持该场景下的两个Ability之间能够完成迁移,可以通过在[module.json5](../quick-start/module-configuration-file.md)文件的abilities标签中配置迁移类型continueType进行关联。 366需要迁移的两个Ability的continueType字段取值必须保持一致,示例如下: 367 > **说明:** 368 > 369 > continueType在本应用中要保证唯一,字符串以字母、数字和下划线组成,最大长度127个字节,不支持中文。 370 > continueType标签类型为字符串数组,如果配置了多个字段,当前仅第一个字段会生效。 371 372```json 373 // 设备A 374 { 375 "module": { 376 // ... 377 "abilities": [ 378 { 379 // ... 380 "name": "Ability-deviceA", 381 "continueType": ['continueType1'], // continueType标签配置 382 } 383 ] 384 } 385 } 386 387 // 设备B 388 { 389 "module": { 390 // ... 391 "abilities": [ 392 { 393 // ... 394 "name": "Ability-deviceB", 395 "continueType": ['continueType1'], // 与设备A相同的continueType标签 396 } 397 ] 398 } 399 } 400``` 401 402### 支持同应用不同BundleName的Ability跨端迁移 403相同应用在不同设备类型下可能使用了不同的BundleName,该场景下如果需要支持应用跨端迁移,需要在不同BundleName的应用的module.json5配置文件中的abilities标签进行如下配置: 404 405- continueBundleName字段:分别添加对端应用的BundleName。 406- continueType字段:必须保持一致。 407 408 > **说明:** 409 > 410 > continueType在本应用中要保证唯一,字符串以字母、数字和下划线组成,最大长度127个字节,不支持中文。 411 > continueType标签类型为字符串数组,如果配置了多个字段,当前仅第一个字段会生效。 412 413示例如下: 414 415 不同BundleName的相同应用在设备A和设备B之间相互迁移,设备A应用的BundleName为com.demo.example1,设备B应用的BundleName为com.demo.example2。 416 417```JSON 418// 在设备A的应用配置文件中,continueBundleName字段配置包含设备B上应用的BundleName。 419{ 420 "module": { 421 // ··· 422 "abilities": [ 423 { 424 "name": "EntryAbility", 425 // ··· 426 "continueType": ["continueType"], 427 "continueBundleName": ["com.demo.example2"], // continueBundleName标签配置,com.demo.example2为设备B上应用的BundleName。 428 429 } 430 ] 431 432 } 433} 434``` 435 436```JSON 437// 在设备B的应用配置文件中,continueBundleName字段配置包含设备A上应用的BundleName。 438{ 439 "module": { 440 // ··· 441 "abilities": [ 442 { 443 "name": "EntryAbility", 444 // ··· 445 "continueType": ["continueType"], 446 "continueBundleName": ["com.demo.example1"], // continueBundleName标签配置,com.demo.example1为设备A上应用的BundleName。 447 448 } 449 ] 450 451 } 452} 453 454``` 455 456### 支持快速拉起目标应用 457默认情况下,发起迁移后不会立即拉起对端的目标应用,而是等待迁移数据从源端同步到对端后,才会拉起。为了发起迁移后能够立即拉起目标应用,做到及时响应,可以通过在continueType标签中添加“_ContinueQuickStart”后缀进行生效,这样待迁移数据从源端同步到对端后只恢复迁移数据即可,提升应用迁移体验。 458 459 ```json 460 { 461 "module": { 462 // ... 463 "abilities": [ 464 { 465 // ... 466 "name": "EntryAbility" 467 "continueType": ['EntryAbility_ContinueQuickStart'], // 如果已经配置了continueType标签,可以在该标签值后添加'_ContinueQuickStart'后缀;如果没有配置continueType标签,可以使用AbilityName + '_ContinueQuickStart'作为continueType标签实现快速拉起目标应用 468 } 469 ] 470 } 471 } 472 ``` 473配置快速拉起功能后,用户触发迁移,等待迁移数据返回的过程中,并行拉起应用,减小用户等待迁移启动时间。同时需注意,应用在迁移的提前启动时,首次触发迁移会收到`launchReason`为提前拉起 (PREPARE_CONTINUATION)的[onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#oncreate)/[onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onnewwant)请求。应用可以通过此`launchReason`,解决跳转、时序等问题,也可以在迁移快速启动时,增加loading界面。 474 475从API version 18开始,在快速拉起时含loading界面的应用,支持[获取应用跨端迁移快速拉起结果](../reference/apis-ability-kit/js-apis-app-ability-continueManager.md#continuemanageron)。根据快速拉起结果,应用可以进行相应操作,例如快速拉起成功时退出loading界面进入接续页面。 476 477快速拉起流程如下图所示。 478 479 480 481配置了快速拉起的应用,触发迁移时会收到两次启动请求,区别如下: 482 483| 场景 | 生命周期函数 | launchParam.launchReason | 484| -------------- | ------------------------------------------- | ------------------------------------------------- | 485| 第一次启动请求 | onCreate (冷启动)<br />或onNewWant (热启动) | AbilityConstant.LaunchReason.PREPARE_CONTINUATION | 486| 第二次启动请求 | onNewWant | AbilityConstant.LaunchReason.CONTINUATION | 487 488如果没有配置快速拉起,则触发迁移时会收到一次启动请求: 489 490| 场景 | 生命周期函数 | launchParam.launchReason | 491| ------------ | ------------------------------------------- | ----------------------------------------- | 492| 一次启动请求 | onCreate (冷启动)<br />或onNewWant (热启动) | AbilityConstant.LaunchReason.CONTINUATION | 493 494配置快速拉起功能后,对应的[onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#oncreate)/[onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onnewwant)接口具体实现,示例代码如下: 495 496```ts 497import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 498import { hilog } from '@kit.PerformanceAnalysisKit'; 499 500const TAG: string = '[MigrationAbility]'; 501const DOMAIN_NUMBER: number = 0xFF00; 502 503export default class MigrationAbility extends UIAbility { 504 storage : LocalStorage = new LocalStorage(); 505 506 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 507 hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Ability onCreate'); 508 509 // 1.已配置快速拉起功能,应用立即启动时触发应用生命周期回调 510 if (launchParam.launchReason === AbilityConstant.LaunchReason.PREPARE_CONTINUATION) { 511 //若应用迁移数据较大,可在此处添加加载页面(页面中显示loading等) 512 //可处理应用自定义跳转、时序等问题 513 // ... 514 } 515 } 516 517 onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { 518 hilog.info(DOMAIN_NUMBER, TAG, 'onNewWant'); 519 520 // 1.已配置快速拉起功能,应用立即启动时触发应用生命周期回调 521 if (launchParam.launchReason === AbilityConstant.LaunchReason.PREPARE_CONTINUATION) { 522 //若应用迁移数据较大,可在此处添加加载页面(页面中显示loading等) 523 //可处理应用自定义跳转、时序等问题 524 // ... 525 } 526 527 // 2.迁移数据恢复时触发应用生命周期回调 528 if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) { 529 // 将上述保存的数据从want.parameters中取出恢复 530 let continueInput = ''; 531 if (want.parameters !== undefined) { 532 continueInput = JSON.stringify(want.parameters.data); 533 hilog.info(DOMAIN_NUMBER, TAG, `continue input ${JSON.stringify(continueInput)}`); 534 } 535 // 触发页面恢复 536 this.context.restoreWindowStage(this.storage); 537 } 538 } 539} 540``` 541 542快速拉起目标应用时,应用的[onWindowStageCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onwindowstagecreate)和[onWindowStageRestore()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onwindowstagerestore)回调会被依次触发。通常在onWindowStageCreate()中,开发者会调用[loadContent()](../reference/apis-arkui/arkts-apis-window-Window.md#loadcontent9)加载页面,该接口会抛出一个异步任务加载首页,该异步任务与onWindowStageRestore()无同步关系。如果在onWindowStageRestore()中使用UI接口(如路由接口),其调用时机可能早于首页加载。为保证正常加载顺序,可以使用[setTimeout()](../reference/common/js-apis-timer.md#settimeout)抛出异步任务执行相关操作。详细见示例代码。 543 544示例代码如下: 545 546```ts 547import { UIAbility } from '@kit.AbilityKit'; 548import { hilog } from '@kit.PerformanceAnalysisKit'; 549import { UIContext, window } from '@kit.ArkUI'; 550 551export default class EntryAbility extends UIAbility { 552 private uiContext: UIContext | undefined = undefined; 553 554 // ... 555 556 onWindowStageCreate(windowStage: window.WindowStage): void { 557 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); 558 559 windowStage.loadContent('pages/Index', (err) => { 560 if (err.code) { 561 hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); 562 return; 563 } 564 this.uiContext = windowStage.getMainWindowSync().getUIContext(); 565 hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); 566 }); 567 } 568 569 onWindowStageRestore(windowStage: window.WindowStage): void { 570 setTimeout(() => { 571 // 抛出异步任务执行路由,保证其执行位于首页加载之后。 572 this.uiContext?.getRouter().pushUrl({ 573 url: 'pages/examplePage' 574 }); 575 }, 0); 576 } 577 578 // ... 579} 580``` 581 582## 跨端迁移中的数据迁移 583 584当前推荐两种不同的数据迁移方式,开发者可以根据实际使用需要进行选择。 585 > **说明:** 586 > 587 > 部分ArkUI组件支持通过配置`restoreId`的方式,在迁移后将特定状态恢复到对端设备。详情请见[分布式迁移标识](../reference/apis-arkui/arkui-ts/ts-universal-attributes-restoreId.md)。 588 > 589 > 如果涉及分布式数据对象迁移时应注意: 590 > 591 > API 11及以前版本涉及分布式数据对象迁移前,需要执行如下操作。 592 > 1. 申请`ohos.permission.DISTRIBUTED_DATASYNC`权限,配置方式请参见[声明权限](../security/AccessToken/declare-permissions.md)。 593 > 594 > 2. 在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/AccessToken/request-user-authorization.md)。 595 596### 使用wantParam迁移数据 597 598在需要迁移的数据较少(100KB以下)时,开发者可以选择在`wantParam`中增加字段进行数据迁移。示例如下: 599 600```ts 601import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 602import { hilog } from '@kit.PerformanceAnalysisKit'; 603 604const TAG: string = '[MigrationAbility]'; 605const DOMAIN_NUMBER: number = 0xFF00; 606 607export default class MigrationAbility extends UIAbility { 608 storage: LocalStorage = new LocalStorage(); 609 610 // 源端保存 611 onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult { 612 // 将要迁移的数据保存在wantParam的自定义字段(例如data)中 613 const continueInput = '迁移的数据'; 614 wantParam['data'] = continueInput; 615 return AbilityConstant.OnContinueResult.AGREE; 616 } 617 618 // 对端恢复 619 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 620 if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) { 621 // 将上述保存的数据取出恢复 622 let continueInput = ''; 623 if (want.parameters !== undefined) { 624 continueInput = JSON.stringify(want.parameters.data); 625 hilog.info(DOMAIN_NUMBER, TAG, `continue input ${continueInput}`); 626 } 627 // 触发页面恢复 628 this.context.restoreWindowStage(this.storage); 629 } 630 } 631 632 onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { 633 if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) { 634 let continueInput = ''; 635 if (want.parameters !== undefined) { 636 continueInput = JSON.stringify(want.parameters.data); 637 hilog.info(DOMAIN_NUMBER, TAG, `continue input ${JSON.stringify(continueInput)}`); 638 } 639 // 触发页面恢复 640 this.context.restoreWindowStage(this.storage); 641 } 642 } 643} 644``` 645 646### 使用分布式数据对象迁移数据 647 648当需要迁移的数据较大(100KB以上)或需要迁移文件时,可以使用[分布式数据对象](../reference/apis-arkdata/js-apis-data-distributedobject.md)。原理与接口说明详见[分布式数据对象跨设备数据同步](../database/data-sync-of-distributed-data-object.md)。 649 650 > **说明:** 651 > 652 > 自API 12起,由于直接使用[跨设备文件访问](../file-management/file-access-across-devices.md)实现文件的迁移难以获取文件同步完成的时间,为了保证更高的成功率,文件数据的迁移不建议继续通过该方式实现,推荐使用分布式数据对象携带资产的方式进行。开发者此前通过跨设备文件访问实现的文件迁移依然生效。 653 654#### 基础数据的迁移 655 656使用分布式数据对象,需要在源端[onContinue()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#oncontinue)接口中进行数据保存,并在对端的[onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#oncreate)/[onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onnewwant)接口中进行数据恢复。 657 658在源端,将需要迁移的数据保存到分布式数据对象[DataObject](../reference/apis-arkdata/js-apis-data-distributedobject.md#dataobject)中。 659 660- 在onContinue()接口中使用[create()](../reference/apis-arkdata/js-apis-data-distributedobject.md#distributeddataobjectcreate9)接口创建分布式数据对象,将所要迁移的数据填充到分布式数据对象数据中。 661- 调用[genSessionId()](../reference/apis-arkdata/js-apis-data-distributedobject.md#distributeddataobjectgensessionid)接口生成数据对象组网id,并使用该id调用[setSessionId()](../reference/apis-arkdata/js-apis-data-distributedobject.md#setsessionid9)加入组网,激活分布式数据对象。 662- 使用[save()](../reference/apis-arkdata/js-apis-data-distributedobject.md#save9)接口将已激活的分布式数据对象持久化,确保源端退出后对端依然可以获取到数据。 663- 将生成的`sessionId`通过`want`传递到对端,供对端激活同步使用。 664 665> **注意** 666> 667> 1. 分布式数据对象需要先激活,再持久化,因此必须在`setSessionId()`后调用save()接口。 668> 2. 对于源端迁移后需要退出的应用,为了防止数据未保存完成应用就退出,应采用`await`的方式等待save()接口执行完毕。从API 12 起,`onContinue()`接口提供了`async`版本供该场景使用。 669> 3. 当前,`wantParams`中`sessionId`字段在迁移流程中被系统占用,建议开发者在`wantParams`中定义其他key值存储该id,避免数据异常。 670 671示例代码如下: 672 673```ts 674// 导入模块 675import { distributedDataObject } from '@kit.ArkData'; 676import { UIAbility, AbilityConstant } from '@kit.AbilityKit'; 677import { BusinessError } from '@kit.BasicServicesKit'; 678import { hilog } from '@kit.PerformanceAnalysisKit'; 679 680const TAG: string = '[MigrationAbility]'; 681const DOMAIN_NUMBER: number = 0xFF00; 682 683// 业务数据定义 684class ParentObject { 685 mother: string 686 father: string 687 688 constructor(mother: string, father: string) { 689 this.mother = mother 690 this.father = father 691 } 692} 693 694// 支持字符、数字、布尔值、对象的传递 695class SourceObject { 696 name: string | undefined 697 age: number | undefined 698 isVis: boolean | undefined 699 parent: ParentObject | undefined 700 701 constructor(name: string | undefined, age: number | undefined, isVis: boolean | undefined, parent: ParentObject | undefined) { 702 this.name = name 703 this.age = age 704 this.isVis = isVis 705 this.parent = parent 706 } 707} 708 709export default class MigrationAbility extends UIAbility { 710 d_object?: distributedDataObject.DataObject; 711 712 async onContinue(wantParam: Record<string, Object>): Promise<AbilityConstant.OnContinueResult> { 713 // ... 714 let parentSource: ParentObject = new ParentObject('jack mom', 'jack Dad'); 715 let source: SourceObject = new SourceObject("jack", 18, false, parentSource); 716 717 // 创建分布式数据对象 718 this.d_object = distributedDataObject.create(this.context, source); 719 720 // 生成数据对象组网id,激活分布式数据对象 721 let dataSessionId: string = distributedDataObject.genSessionId(); 722 this.d_object.setSessionId(dataSessionId); 723 724 // 将组网id存在want中传递到对端 725 wantParam['dataSessionId'] = dataSessionId; 726 727 // 数据对象持久化,确保迁移后即使应用退出,对端依然能够恢复数据对象 728 // 从wantParam.targetDevice中获取到对端设备的networkId作为入参 729 await this.d_object.save(wantParam.targetDevice as string).then((result: 730 distributedDataObject.SaveSuccessResponse) => { 731 hilog.info(DOMAIN_NUMBER, TAG, `Succeeded in saving. SessionId: ${result.sessionId}`, 732 `version:${result.version}, deviceId:${result.deviceId}`); 733 }).catch((err: BusinessError) => { 734 hilog.error(DOMAIN_NUMBER, TAG, 'Failed to save. Error: ', JSON.stringify(err) ?? ''); 735 }); 736 737 return AbilityConstant.OnContinueResult.AGREE; 738 } 739} 740``` 741 742对端在[onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#oncreate)/[onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onnewwant)中,通过加入与源端一致的分布式数据对象组网进行数据恢复。 743 744- 创建空的分布式数据对象,用于接收恢复的数据。 745- 从[want](../reference/apis-ability-kit/js-apis-app-ability-want.md)中读取分布式数据对象组网id。 746- 注册[on()](../reference/apis-arkdata/js-apis-data-distributedobject.md#onstatus9)接口监听数据变更。在收到`status`为`restore`的事件的回调中,实现数据恢复完毕时需要进行的业务操作。 747- 调用[setSessionId()](../reference/apis-arkdata/js-apis-data-distributedobject.md#setsessionid9)加入组网,激活分布式数据对象。 748 749> **注意** 750> 751> 1. 对端加入组网的分布式数据对象不能为临时变量,因为on()接口的回调可能在onCreate()/onNewWant()执行结束后才执行,临时变量被释放可能导致空指针异常。可以使用类成员变量避免该问题。 752> 2. 对端用于创建分布式数据对象的`Object`,其属性应在激活分布式数据对象前置为`undefined`,否则会导致新数据加入组网后覆盖源端数据,数据恢复失败。 753> 3. 应当在激活分布式数据对象之前,调用on()接口进行注册监听,防止错过`restore`事件导致数据恢复失败。 754 755示例代码如下: 756 757```ts 758import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 759import { distributedDataObject } from '@kit.ArkData'; 760import { hilog } from '@kit.PerformanceAnalysisKit'; 761 762const TAG: string = '[MigrationAbility]'; 763const DOMAIN_NUMBER: number = 0xFF00; 764 765// 示例数据对象定义与上同 766export default class MigrationAbility extends UIAbility { 767 d_object?: distributedDataObject.DataObject; 768 769 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 770 if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) { 771 // ... 772 // 调用封装好的分布式数据对象处理函数 773 this.handleDistributedData(want); 774 } 775 } 776 777 onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { 778 if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) { 779 if (want.parameters !== undefined) { 780 // ... 781 // 调用封装好的分布式数据对象处理函数 782 this.handleDistributedData(want); 783 } 784 } 785 } 786 787 handleDistributedData(want: Want) { 788 // 创建空的分布式数据对象 789 let remoteSource: SourceObject = new SourceObject(undefined, undefined, undefined, undefined); 790 this.d_object = distributedDataObject.create(this.context, remoteSource); 791 792 // 读取分布式数据对象组网id 793 let dataSessionId = ''; 794 if (want.parameters !== undefined) { 795 dataSessionId = want.parameters.dataSessionId as string; 796 } 797 798 // 添加数据变更监听 799 this.d_object.on("status", (sessionId: string, networkId: string, status: 'online' | 'offline' | 'restored') => { 800 hilog.info(DOMAIN_NUMBER, TAG, "status changed " + sessionId + " " + status + " " + networkId); 801 if (status == 'restored') { 802 if (this.d_object) { 803 // 收到迁移恢复的状态时,可以从分布式数据对象中读取数据 804 hilog.info(DOMAIN_NUMBER, TAG, "restored name:" + this.d_object['name']); 805 hilog.info(DOMAIN_NUMBER, TAG, "restored parents:" + JSON.stringify(this.d_object['parent'])); 806 } 807 } 808 }); 809 810 // 激活分布式数据对象 811 this.d_object.setSessionId(dataSessionId); 812 } 813} 814``` 815 816#### 文件资产的迁移 817 818对于图片、文档等文件类数据,需要先将其转换为[资产`commonType.Asset`](../reference/apis-arkdata/js-apis-data-commonType.md#asset)类型,再封装到分布式数据对象中进行迁移。迁移实现方式与普通的分布式数据对象类似,下例中仅针对区别部分进行说明。 819 820在源端,将需要迁移的文件资产保存到分布式数据对象[DataObject](../reference/apis-arkdata/js-apis-data-distributedobject.md#dataobject)中。 821 822- 将文件资产拷贝到[分布式文件目录](application-context-stage.md#获取应用文件路径)下,相关接口与用法详见[基础文件接口](../file-management/app-file-access.md)。 823- 使用分布式文件目录下的文件创建`Asset`资产对象。 824- 将`Asset`资产对象作为分布式数据对象的根属性保存。 825 826随后,与普通数据对象的迁移的源端实现相同,可以使用该数据对象加入组网,并进行持久化保存。 827 828示例如下: 829 830```ts 831// 导入模块 832import { UIAbility, AbilityConstant } from '@kit.AbilityKit'; 833import { distributedDataObject, commonType } from '@kit.ArkData'; 834import { fileIo, fileUri } from '@kit.CoreFileKit'; 835import { hilog } from '@kit.PerformanceAnalysisKit'; 836import { BusinessError } from '@ohos.base'; 837 838const TAG: string = '[MigrationAbility]'; 839const DOMAIN_NUMBER: number = 0xFF00; 840 841// 数据对象定义 842class ParentObject { 843 mother: string 844 father: string 845 846 constructor(mother: string, father: string) { 847 this.mother = mother 848 this.father = father 849 } 850} 851 852class SourceObject { 853 name: string | undefined 854 age: number | undefined 855 isVis: boolean | undefined 856 parent: ParentObject | undefined 857 attachment: commonType.Asset | undefined // 新增资产属性 858 859 constructor(name: string | undefined, age: number | undefined, isVis: boolean | undefined, 860 parent: ParentObject | undefined, attachment: commonType.Asset | undefined) { 861 this.name = name 862 this.age = age 863 this.isVis = isVis 864 this.parent = parent 865 this.attachment = attachment; 866 } 867} 868 869export default class MigrationAbility extends UIAbility { 870 d_object?: distributedDataObject.DataObject; 871 872 async onContinue(wantParam: Record<string, Object>): Promise<AbilityConstant.OnContinueResult> { 873 // ... 874 875 // 1. 将资产写入分布式文件目录下 876 let distributedDir: string = this.context.distributedFilesDir; // 获取分布式文件目录路径 877 let fileName: string = '/test.txt'; // 文件名 878 let filePath: string = distributedDir + fileName; // 文件路径 879 880 try { 881 // 在分布式目录下创建文件 882 let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE); 883 hilog.info(DOMAIN_NUMBER, TAG, 'Create file success.'); 884 // 向文件中写入内容(若资产为图片,可将图片转换为buffer后写入) 885 fileIo.writeSync(file.fd, '[Sample] Insert file content here.'); 886 // 关闭文件 887 fileIo.closeSync(file.fd); 888 } catch (error) { 889 let err: BusinessError = error as BusinessError; 890 hilog.error(DOMAIN_NUMBER, TAG, `Failed to openSync / writeSync / closeSync. Code: ${err.code}, message: ${err.message}`); 891 } 892 893 // 2. 使用分布式文件目录下的文件创建资产对象 894 let distributedUri: string = fileUri.getUriFromPath(filePath); // 获取分布式文件Uri 895 896 // 获取文件参数 897 let ctime: string = ''; 898 let mtime: string = ''; 899 let size: string = ''; 900 await fileIo.stat(filePath).then((stat: fileIo.Stat) => { 901 ctime = stat.ctime.toString(); // 创建时间 902 mtime = stat.mtime.toString(); // 修改时间 903 size = stat.size.toString(); // 文件大小 904 }) 905 906 // 创建资产对象 907 let attachment: commonType.Asset = { 908 name: fileName, 909 uri: distributedUri, 910 path: filePath, 911 createTime: ctime, 912 modifyTime: mtime, 913 size: size, 914 } 915 916 // 3. 将资产对象作为分布式数据对象的根属性,创建分布式数据对象 917 let parentSource: ParentObject = new ParentObject('jack mom', 'jack Dad'); 918 let source: SourceObject = new SourceObject("jack", 18, false, parentSource, attachment); 919 this.d_object = distributedDataObject.create(this.context, source); 920 921 // 生成组网id,激活分布式数据对象,save持久化保存 922 // ... 923 924 return AbilityConstant.OnContinueResult.AGREE; 925 } 926} 927``` 928 929对端需要先创建一个各属性为空的`Asset`资产对象作为分布式数据对象的根属性。在接收到[on()](../reference/apis-arkdata/js-apis-data-distributedobject.md#onstatus9)接口`status`为`restored`的事件的回调时,表示包括资产在内的数据同步完成,可以像获取基本数据一样获取到源端的资产对象。 930 931> **注意** 932> 933> 对端创建分布式数据对象时,`SourceObject`对象中的资产不能直接使用`undefined`初始化,需要创建一个所有属性初始值为空的Asset资产对象,使分布式对象可以识别出资产类型。 934 935```ts 936import { UIAbility, Want } from '@kit.AbilityKit'; 937import { distributedDataObject, commonType } from '@kit.ArkData'; 938import { hilog } from '@kit.PerformanceAnalysisKit'; 939 940const TAG: string = '[MigrationAbility]'; 941const DOMAIN_NUMBER: number = 0xFF00; 942 943export default class MigrationAbility extends UIAbility { 944 d_object?: distributedDataObject.DataObject; 945 946 handleDistributedData(want: Want) { 947 // ... 948 // 创建一个各属性为空的资产对象 949 let attachment: commonType.Asset = { 950 name: '', 951 uri: '', 952 path: '', 953 createTime: '', 954 modifyTime: '', 955 size: '', 956 } 957 958 // 使用该空资产对象创建分布式数据对象,其余基础属性可以直接使用undefined 959 let source: SourceObject = new SourceObject(undefined, undefined, undefined, undefined, attachment); 960 this.d_object = distributedDataObject.create(this.context, source); 961 962 this.d_object.on("status", (sessionId: string, networkId: string, status: 'online' | 'offline' | 'restored') => { 963 if (status == 'restored') { 964 if (this.d_object) { 965 // 收到监听的restored回调,表示分布式资产对象同步完成 966 hilog.info(DOMAIN_NUMBER, TAG, "restored attachment:" + JSON.stringify(this.d_object['attachment'])); 967 } 968 } 969 }); 970 // ... 971 } 972} 973``` 974 975若应用想要同步多个资产,可采用两种方式实现 976 9771. 可将每个资产作为分布式数据对象的一个根属性实现,适用于要迁移的资产数量固定的场景。 9782. 可以将资产数组传化为`Object`传递,适用于需要迁移的资产个数会动态变化的场景(如,用户选择了不定数量的图片)。当前不支持直接将资产数组作为根属性传递。 979 980其中方式1的实现可以直接参照添加一个资产的方式添加更多资产。方式2的示例如下所示: 981 982```ts 983// 导入模块 984import { distributedDataObject, commonType } from '@kit.ArkData'; 985import { UIAbility } from '@kit.AbilityKit'; 986 987// 数据对象定义 988class SourceObject { 989 name: string | undefined 990 assets: Object | undefined // 分布式数据对象的中添加一个Object属性 991 992 constructor(name: string | undefined, assets: Object | undefined) { 993 this.name = name 994 this.assets = assets; 995 } 996} 997 998export default class MigrationAbility extends UIAbility { 999 d_object?: distributedDataObject.DataObject; 1000 1001 // 该函数用于将资产数组转为Record 1002 GetAssetsWapper(assets: commonType.Assets): Record<string, commonType.Asset> { 1003 let wrapper: Record<string, commonType.Asset> = {} 1004 let num: number = assets.length; 1005 for (let i: number = 0; i < num; i++) { 1006 wrapper[`asset${i}`] = assets[i]; 1007 } 1008 return wrapper; 1009 } 1010 1011 async onContinue(wantParam: Record<string, Object>): Promise<AbilityConstant.OnContinueResult> { 1012 // ... 1013 1014 // 创建了多个资产对象 1015 let attachment1: commonType.Asset = { 1016 name: '', 1017 uri: '', 1018 path: '', 1019 createTime: '', 1020 modifyTime: '', 1021 size: '', 1022 } 1023 1024 let attachment2: commonType.Asset = { 1025 name: '', 1026 uri: '', 1027 path: '', 1028 createTime: '', 1029 modifyTime: '', 1030 size: '', 1031 } 1032 1033 // 将资产对象插入资产数组 1034 let assets: commonType.Assets = []; 1035 assets.push(attachment1); 1036 assets.push(attachment2); 1037 1038 // 将资产数组转为Record Object,并用于创建分布式数据对象 1039 let assetsWrapper: Object = this.GetAssetsWapper(assets); 1040 let source: SourceObject = new SourceObject("jack", assetsWrapper); 1041 this.d_object = distributedDataObject.create(this.context, source); 1042 1043 // ... 1044 return AbilityConstant.OnContinueResult.AGREE; 1045 } 1046} 1047``` 1048 1049## 验证指导 1050 1051为方便开发者验证已开发的可迁移应用,系统提供了一个全局任务中心demo应用`MissionCenter`作为迁移的入口。下面介绍通过安装全局任务中心来验证迁移的方式。 1052 1053> **说明:** 1054> 1055> 本文中的截图仅为参考,具体的显示界面请以实际使用的DevEco Studio和SDK的版本为准。 1056 10571. 编译安装全局任务中心。 1058 1059 1. 为了正确编译安装全局任务中心,开发者需要替换Full-SDK,具体操作可参见[替换指南](../faqs/full-sdk-switch-guide.md)。 1060 1061 2. 下载[MissionCenter_Demo](https://gitee.com/openharmony/ability_dmsfwk/tree/master/services/dtbschedmgr/test/missionCenterDemo/dmsDemo/entry/src/main)示例代码。 1062 1063 3. 编译工程文件。 1064 1065 1. 新建一个工程,找到对应的文件夹替换下载文件。 1066 1067  1068 1069 2. 自动签名,编译安装。 1070 DevEco的自动签名模板默认签名权限为`normal`级。而本应用所需`ohos.permission.MANAGE_MISSIONS`权限为`system_core`级别。自动生成的签名无法获得足够的权限,所以需要将权限升级为`system_core`级别,然后签名。 1071 1. 将Sdk目录下的`openharmony\api版本 (如:10)\toolchains\lib\UnsignedReleasedProfileTemplate.json`文件中的`"apl":"normal"`改为`"apl":"system_core"`。 1072 1073 2. 点击 **file->Project Structure**。 1074 1075  1076 1077 3. 点击 **Signing Configs** 点击 **OK**。 1078 1079  1080 1081 3. 连接开发板运行生成demo。 1082 10832. 设备组网。 1084 1085 1. 打开A,B两设备的计算器。 1086 2. 点击右上角箭头选择B设备。 1087 3. 在B设备选择信任设备,弹出PIN码。 1088 4. 在A设备输入PIN码。 1089 5. 已组网成功,验证方法:在A设备输入数字,B设备同步出现则证明组网成功。 1090 10913. 发起迁移。 1092 1093 1. 在B设备打开多设备协同权限的应用,A设备打开全局任务中心demo,demo显示A设备名称和B设备名称。 1094 2. 点击B设备名称,列表显示B设备的应用卡片列表。 1095 3. 将要接续的应用卡片拖拽到A设备名称处,A设备应用被拉起。 1096 1097## 常见问题 1098 1099### Q1:迁移之后的应用,无法重新迁移回源端 1100 1101由于迁移状态的设置是Ability级别的,对端拉起的应用可能执行过自己的迁移状态设置命令(例如,冷启动时对端在[onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#oncreate)中设置了 **INACTIVE** ;热启动时对端已打开了不可迁移的页面,迁移状态为 **INACTIVE** 等情况)。为了保证迁移过后的应用依然具有可以迁移回源端的能力,应在 onCreate()/[onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onnewwant)的迁移调用判断中,将迁移状态设置为 **ACTIVE** 。 1102 1103```ts 1104// MigrationAbility.ets 1105import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 1106import { hilog } from '@kit.PerformanceAnalysisKit'; 1107 1108const TAG: string = '[MigrationAbility]'; 1109const DOMAIN_NUMBER: number = 0xFF00; 1110 1111export default class MigrationAbility extends UIAbility { 1112 // ... 1113 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 1114 // ... 1115 if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) { 1116 // ... 1117 // 调用原因为迁移时,设置状态为可迁移,应对冷启动情况 1118 this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => { 1119 hilog.info(DOMAIN_NUMBER, TAG, `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`); 1120 }); 1121 } 1122 // ... 1123 } 1124 1125 onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { 1126 // ... 1127 // 调用原因为迁移时,设置状态为可迁移,应对热启动情况 1128 if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) { 1129 this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => { 1130 hilog.info(DOMAIN_NUMBER, TAG, `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`); 1131 }); 1132 } 1133 } 1134 // ... 1135} 1136``` 1137 1138### Q2:在onWindowStageRestore()中调用loadContent()没有生效 1139 1140如果应用没有配置关闭页面栈迁移能力,系统默认对应用的页面栈进行迁移与加载。这种情况下,如果开发者在生命周期函数[onWindowStageRestore()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onwindowstagerestore)中再次通过[loadContent()](../reference/apis-arkui/arkts-apis-window-Window.md#loadcontent9)等方式触发指定页面加载,则这次加载不会生效,依然恢复页面栈中的页面。 1141 1142## 相关实例 1143 1144针对跨端迁移的开发,有以下相关实例可供参考: 1145 1146[跨端迁移随手记(ArkTS)(Public SDK)(API12)](https://gitcode.com/openharmony/applications_app_samples/tree/master/code/SuperFeature/DistributedAppDev/DistributedJotNote) 1147