• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![hop-cross-device-migration](figures/hop-cross-device-migration.png)
31
321. 在源端,通过`UIAbility`的[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncontinue)回调,开发者可以保存待接续的业务数据。例如,在浏览器应用中完成跨端迁移,开发者需要使用[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncontinue)回调保存页面URL等业务内容,而系统将自动保存页面状态,如当前浏览进度。
332. 分布式框架提供了跨设备应用界面、页面栈以及业务数据的保存和恢复机制,它负责将数据从源端发送到对端。
343. 在对端,同一`UIAbility`可以通过[`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)(冷启动)和[`onNewWant()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityonnewwant)(热启动)接口来恢复业务数据。
35
36
37## 约束限制
38
39- 跨端迁移要求在同一`UIAbility`之间进行,也就是需要相同的`bundleName`、`abilityName`和签名信息。
40- 为了获得最佳体验,使用`wantParam`传输的数据需要控制在100KB以下。
41
42## 开发步骤
43
441. 在[module.json5配置文件](../quick-start/module-configuration-file.md)的abilities标签中配置跨端迁移标签`continuable`。
45
46   ```json
47   {
48     "module": {
49       // ...
50       "abilities": [
51         {
52           // ...
53           "continuable": true, // 配置UIAbility支持迁移
54         }
55       ]
56     }
57   }
58   ```
59
60   > **说明:**
61   >
62   > 根据需要配置应用启动模式类型,配置详情请参照[UIAbility组件启动模式](uiability-launch-type.md)。
63
642. 在源端`UIAbility`中实现[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncontinue)回调。
65
66   当`UIAbility`实例触发迁移时,[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncontinue)回调在源端被调用,开发者可以在该接口中保存迁移数据,实现应用兼容性检测,决定是否支持此次迁移。
67
68   - 保存迁移数据:开发者可以将要迁移的数据通过键值对的方式保存在`wantParam`参数中。
69   - 应用兼容性检测:开发者可以通过从`wantParam`参数中获取对端应用的版本号与源端应用版本号做兼容性校验。开发者可以在触发迁移时从[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncontinue)回调中`wantParam.version`获取到迁移对端应用的版本号与迁移源端应用版本号做兼容校验。
70   - 迁移决策:开发者可以通过[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncontinue)回调的返回值决定是否支持此次迁移。
71
72   ```ts
73   import AbilityConstant from '@ohos.app.ability.AbilityConstant';
74   import hilog from '@ohos.hilog';
75   import UIAbility from '@ohos.app.ability.UIAbility';
76
77   const TAG: string = '[MigrationAbility]';
78   const DOMAIN_NUMBER: number = 0xFF00;
79
80   export default class MigrationAbility extends UIAbility {
81     onContinue(wantParam: Record<string, Object>):AbilityConstant.OnContinueResult {
82       let version = wantParam.version;
83       let targetDevice = wantParam.targetDevice;
84       hilog.info(DOMAIN_NUMBER, TAG, `onContinue version = ${version}, targetDevice: ${targetDevice}`); // 准备迁移数据
85
86       // 获取源端版本号
87       let versionSrc: number = -1; // 请填充具体获取版本号的代码
88
89       // 兼容性校验
90       if (version !== versionSrc) {
91         // 在兼容性校验不通过时返回MISMATCH
92         return AbilityConstant.OnContinueResult.MISMATCH;
93       }
94
95       // 将要迁移的数据保存在wantParam的自定义字段(例如data)中
96       const continueInput = '迁移的数据';
97       wantParam['data'] = continueInput;
98
99       return AbilityConstant.OnContinueResult.AGREE;
100     }
101   }
102   ```
103
1043. 源端设备`UIAbility`实例在冷启动和热启动情况下分别会调用不同的接口来恢复数据和加载UI。
105   在对端设备的`UIAbility`中,需要实现[`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)/[`onNewWant()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityonnewwant)接口来恢复迁移数据。
106
107   通过在[`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)/[`onNewWant()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityonnewwant)回调中检查`launchReason`,可以判断此次启动是否有迁移触发。开发者可以从`want`中获取之前保存的迁移数据,并在数据恢复后调用`restoreWindowStage()`来触发页面恢复,包括页面栈信息。
108
109   ```ts
110   import AbilityConstant from '@ohos.app.ability.AbilityConstant';
111   import hilog from '@ohos.hilog';
112   import UIAbility from '@ohos.app.ability.UIAbility';
113   import type Want from '@ohos.app.ability.Want';
114
115   const TAG: string = '[MigrationAbility]';
116   const DOMAIN_NUMBER: number = 0xFF00;
117
118   export default class MigrationAbility extends UIAbility {
119     storage : LocalStorage = new LocalStorage();
120
121     onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
122       hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Ability onCreate');
123       if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
124         // 将上述保存的数据从want.parameters中取出恢复
125         let continueInput = '';
126         if (want.parameters !== undefined) {
127           continueInput = JSON.stringify(want.parameters.data);
128           hilog.info(DOMAIN_NUMBER, TAG, `continue input ${continueInput}`);
129         }
130         // 触发页面恢复
131         this.context.restoreWindowStage(this.storage);
132       }
133     }
134
135     onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
136        hilog.info(DOMAIN_NUMBER, TAG, 'onNewWant');
137        if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
138          // 将上述保存的数据从want.parameters中取出恢复
139          let continueInput = '';
140          if (want.parameters !== undefined) {
141            continueInput = JSON.stringify(want.parameters.data);
142            hilog.info(DOMAIN_NUMBER, TAG, `continue input ${continueInput}`);
143          }
144          // 触发页面恢复
145          this.context.restoreWindowStage(this.storage);
146        }
147      }
148   }
149   ```
150
151## 可选配置迁移能力
152
153### 动态配置迁移能力
154
155从API version 10开始,提供了支持动态配置迁移能力的功能。即应用可以根据实际使用场景,在需要迁移时开启应用迁移能力;在业务不需要迁移时则可以关闭迁移能力。
156
157开发者可以通过调用[`setMissionContinueState()`](../reference/apis/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextsetmissioncontinuestate10)接口对迁移能力进行设置。默认状态下,应用的迁移能力为**ACTIVE**状态,即迁移能力开启,可以迁移。
158
159**设置迁移能力的时机**
160
161迁移能力的改变可以根据实际业务需求和代码实现,发生在应用生命周期的绝大多数时机。本文介绍常用的几种配置方式。
162
163在`UIAbility`的[`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)回调中调用接口,可以在应用创建时设置应用的迁移状态。
164
165```ts
166// MigrationAbility.ets
167import AbilityConstant from '@ohos.app.ability.AbilityConstant';
168import hilog from '@ohos.hilog';
169import UIAbility from '@ohos.app.ability.UIAbility';
170import type Want from '@ohos.app.ability.Want';
171
172const TAG: string = '[MigrationAbility]';
173const DOMAIN_NUMBER: number = 0xFF00;
174
175export default class MigrationAbility extends UIAbility {
176  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
177    // ...
178    this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) => {
179      hilog.info(DOMAIN_NUMBER, TAG, `setMissionContinueState: ${JSON.stringify(result)}`);
180    });
181    // ...
182  }
183}
184```
185
186在页面的`onPageShow()`回调中调用接口,可以设置单个页面出现时应用的迁移状态。
187
188```ts
189// Page_MigrationAbilityFirst.ets
190import AbilityConstant from '@ohos.app.ability.AbilityConstant';
191import common from '@ohos.app.ability.common';
192import hilog from '@ohos.hilog';
193
194const TAG: string = '[MigrationAbility]';
195const DOMAIN_NUMBER: number = 0xFF00;
196
197@Entry
198@Component
199struct Page_MigrationAbilityFirst {
200  private context = getContext(this) as common.UIAbilityContext;
201  build() {
202    // ...
203  }
204  // ...
205  onPageShow(){
206    // 进入该页面时,将应用设置为可迁移状态
207    this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
208      hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
209    });
210  }
211}
212```
213
214在某个组件的触发事件中设置应用迁移能力。
215
216```ts
217// Page_MigrationAbilityFirst.ets
218import AbilityConstant from '@ohos.app.ability.AbilityConstant';
219import common from '@ohos.app.ability.common';
220import hilog from '@ohos.hilog';
221import promptAction from '@ohos.promptAction';
222
223const TAG: string = '[MigrationAbility]';
224const DOMAIN_NUMBER: number = 0xFF00;
225
226@Entry
227@Component
228struct Page_MigrationAbilityFirst {
229  private context = getContext(this) as common.UIAbilityContext;
230  build() {
231    // ...
232    Button() {
233      // ...
234    }
235    .onClick(()=>{
236      // 点击该按钮时,将应用设置为可迁移状态
237      this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
238        hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
239      });
240    })
241  }
242}
243```
244
245### **保证迁移连续性**
246
247由于迁移加载时,对端拉起的应用可能执行过自己的迁移状态设置命令(例如,冷启动时对端在[`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#uiabilityonnewwant)的迁移调用判断中,将迁移状态设置为 **ACTIVE** 。
248
249```ts
250// MigrationAbility.ets
251import AbilityConstant from '@ohos.app.ability.AbilityConstant';
252import hilog from '@ohos.hilog';
253import UIAbility from '@ohos.app.ability.UIAbility';
254import type Want from '@ohos.app.ability.Want';
255
256const TAG: string = '[MigrationAbility]';
257const DOMAIN_NUMBER: number = 0xFF00;
258
259export default class MigrationAbility extends UIAbility {
260  // ...
261  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
262    // ...
263    // 调用原因为迁移时,设置状态为可迁移,应对冷启动情况
264    if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
265      this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
266        hilog.info(`setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
267      });
268    }
269  }
270
271  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
272    // ...
273    // 调用原因为迁移时,设置状态为可迁移,应对热启动情况
274    if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
275      this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
276        hilog.info(DOMAIN_NUMBER, TAG, `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
277      });
278    }
279  }
280  // ...
281}
282```
283
284### 按需迁移页面栈
285
286支持应用动态选择是否进行页面栈恢复(默认进行页面栈信息恢复)。如果应用不想使用系统默认恢复的页面栈,则可以设置不进行页面栈迁移,而需要在`onWindowStageRestore()`设置迁移后进入的页面,参数定义见[SUPPORT_CONTINUE_PAGE_STACK_KEY](../reference/apis/js-apis-app-ability-wantConstant.md#wantconstantparams)。
287
288应用在源端的页面栈中存在Index和Second路由,而在对端恢复时不需要按照源端页面栈进行恢复,需要恢复到指定页面。
289
290例如,`UIAbility`迁移不需要自动迁移页面栈信息。
291
292```ts
293// MigrationAbility.ets
294import AbilityConstant from '@ohos.app.ability.AbilityConstant';
295import hilog from '@ohos.hilog';
296import UIAbility from '@ohos.app.ability.UIAbility';
297import wantConstant from '@ohos.app.ability.wantConstant';
298import type window from '@ohos.window';
299
300const TAG: string = '[MigrationAbility]';
301const DOMAIN_NUMBER: number = 0xFF00;
302
303export default class MigrationAbility extends UIAbility {
304  // ...
305  onContinue(wantParam: Record<string, Object>):AbilityConstant.OnContinueResult {
306    hilog.info(DOMAIN_NUMBER, TAG, `onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`);
307    wantParam[wantConstant.Params.SUPPORT_CONTINUE_PAGE_STACK_KEY] = false;
308    return AbilityConstant.OnContinueResult.AGREE;
309  }
310
311  onWindowStageRestore(windowStage: window.WindowStage) : void {
312    // 若不需要自动迁移页面栈信息,则需要在此处设置应用迁移后进入的页面
313    windowStage.loadContent('pages/page_migrationability/Page_MigrationAbilityThird', (err, data) => {
314      if (err.code) {
315        hilog.error(DOMAIN_NUMBER, TAG, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
316        return;
317      }
318    });
319  }
320}
321```
322
323### 按需退出
324
325支持应用动态选择迁移成功后是否退出迁移源端应用(默认迁移成功后退出迁移源端应用)。如果应用不想让系统自动退出迁移源端应用,则可以设置不退出,参数定义见[SUPPORT_CONTINUE_SOURCE_EXIT_KEY](../reference/apis/js-apis-app-ability-wantConstant.md#wantconstantparams)。
326
327示例:`UIAbility`设置迁移成功后,源端不需要退出迁移应用。
328
329```ts
330import AbilityConstant from '@ohos.app.ability.AbilityConstant';
331import hilog from '@ohos.hilog';
332import UIAbility from '@ohos.app.ability.UIAbility';
333import wantConstant from '@ohos.app.ability.wantConstant';
334
335const TAG: string = '[MigrationAbility]';
336const DOMAIN_NUMBER: number = 0xFF00;
337
338export default class MigrationAbility extends UIAbility {
339  // ...
340  onContinue(wantParam: Record<string, Object>):AbilityConstant.OnContinueResult {
341    hilog.info(DOMAIN_NUMBER, TAG, `onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`);
342    wantParam[wantConstant.Params.SUPPORT_CONTINUE_SOURCE_EXIT_KEY] = false;
343    return AbilityConstant.OnContinueResult.AGREE;
344  }
345}
346```
347
348## 跨端迁移中的数据迁移
349当前支持三种不同的数据迁移方式,开发者可以根据实际使用需要进行选择。
350  > **说明:**
351  >
352  > 部分ArkUI组件支持通过配置`restoreId`的方式,在迁移后将特定状态恢复到对端设备。详情请见[分布式迁移标识](../../application-dev/reference/arkui-ts/ts-universal-attributes-restoreId.md)。
353  >
354  > 如果涉及分布式对象和分布式文件迁移时应注意:
355  >
356  > 1. 需要申请`ohos.permission.DISTRIBUTED_DATASYNC`权限,配置方式请参见[配置文件权限声明](../security/accesstoken-guidelines.md#配置文件权限声明)。
357  > 2. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/accesstoken-guidelines.md#向用户申请授权)。
358
359### 使用wantParam迁移数据
360
361在需要迁移的数据较少(100KB以下)时,开发者可以选择在`wantParam`中增加字段进行数据迁移。示例如下:
362
363```ts
364import UIAbility from '@ohos.app.ability.UIAbility';
365import AbilityConstant from '@ohos.app.ability.AbilityConstant';
366import Want from '@ohos.app.ability.Want';
367
368const TAG: string = '[MigrationAbility]';
369const DOMAIN_NUMBER: number = 0xFF00;
370
371export default class MigrationAbility extends UIAbility {
372  // 源端保存
373  onContinue(wantParam: Record<string, Object>):AbilityConstant.OnContinueResult {
374    // 将要迁移的数据保存在wantParam的自定义字段(例如data)中
375    const continueInput = '迁移的数据';
376    wantParam['data'] = continueInput;
377    return AbilityConstant.OnContinueResult.AGREE;
378  }
379
380  // 对端恢复
381  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
382    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
383      // 将上述保存的数据取出恢复
384      let continueInput = '';
385      if (want.parameters !== undefined) {
386        continueInput = JSON.stringify(want.parameters.data);
387        hilog.info(DOMAIN_NUMBER, TAG, `continue input ${continueInput}`);
388      }
389      // 触发页面恢复
390      this.context.restoreWindowStage(this.storage);
391    }
392  }
393
394  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
395    if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
396      let continueInput = '';
397      if (want.parameters !== undefined) {
398        continueInput = JSON.stringify(want.parameters.data);
399        hilog.info(DOMAIN_NUMBER, TAG, `continue input ${JSON.stringify(continueInput)}`);
400      }
401      // 触发页面恢复
402      this.context.restoreWindowStage(this.storage);
403    }
404  }
405}
406```
407### 使用分布式对象迁移数据
408
409当需要迁移的数据较大(100KB以上)时,可以选择[分布式对象](../../application-dev/reference/apis/js-apis-data-distributedobject.md)进行数据迁移。
410
4111. 在源端`onContinue()`接口中创建一个分布式数据对象[`DataObject`](../../application-dev/reference/apis/js-apis-data-distributedobject.md#dataobject),将所要迁移的数据填充到分布式对象数据中,并将生成的`sessionId`通过`want`传递到对端。
4122. 对端在`onCreate()/onNewWant`中进行数据恢复时,可以从want中读取该`sessionId`,通过分布式对象恢复数据。
413
414使用参考详见[分布式数据对象跨设备数据同步](../../application-dev/database/data-sync-of-distributed-data-object.md)。
415
416### 使用分布式文件迁移数据
417当需要迁移的数据较大(100KB以上)时,也可以选择分布式文件进行数据迁移。相比于分布式对象,分布式文件更适用于需要传输的数据为文件的场景。在源端将数据写入分布式文件路径后,对端迁移后拉起的应用能够在同个分布式文件路径下访问到该文件。
418
419使用参考详见[跨设备文件访问](../../application-dev/file-management/file-access-across-devices.md)。
420
421## 验证指导
422
423为方便开发者验证已开发的可迁移应用,系统提供了一个全局任务中心demo作为迁移的入口。下面介绍通过安装全局任务中心来验证迁移的方式。
424
425### 1. 编译安装全局任务中心
426
427#### **配置环境**
428
429public-SDK不支持开发者使用所有的系统API,例如:全局任务中心使用的[**@ohos.distributedDeviceManager**](../../application-dev/reference/apis/js-apis-distributedDeviceManager.md)不包括在public_SDK中。因此为了正确编译安装全局任务中心,开发者需要替换full-SDK,具体操作可参见[替换指南](../../application-dev/faqs/full-sdk-switch-guide.md)。
430
431> **说明**:
432>
433> 本文中的截图仅为参考,具体的显示界面请以实际使用的DevEco Studio和SDK的版本为准。
434
435#### **下载MissionCenter_Demo[示例代码](https://gitee.com/openharmony/ability_dmsfwk/tree/master/services/dtbschedmgr/test/missionCenterDemo/dmsDemo/entry/src/main)**
436
437#### **编译工程文件**
438
439​	a.新建一个工程,找到对应的文件夹替换下载文件
440![hop-cross-device-migration](figures/hop-cross-device-migration1.png)
441
442​	b.自动签名,编译安装。
443
444​		DevEco的自动签名模板默认签名权限为normal级。而本应用设计到ohos.permission.MANAGE_MISSIONS权限为system_core级别。自动生成的签名无法获得足够的权限,所以需要将权限升级为system_core级别,然后签名。
445
446​	c.系统权限设置(以api10目录为例): 将Sdk目录下的openharmony\api版本(如:10)\toolchains\lib\UnsignedReleasedProfileTemplate.json文件中的"apl":"normal"改为"apl":"system_core"。
447
4481. 点击file->Project Structure。
449   ![hop-cross-device-migration](figures/hop-cross-device-migration2.png)
4502. 点击Signing Configs  点击OK。
451   ![hop-cross-device-migration](figures/hop-cross-device-migration3.png)
4523. 连接开发板运行生成demo。
453
454### 2. 设备组网
455
4561. 打开A,B两设备的计算器。
4572. 点击右上角箭头选择B设备。
4583. 在B设备选择信任设备,弹出PIN码。
4594. 在A设备输入PIN码。
4605. 已组网成功,验证方法:在A设备输入数字,B设备同步出现则证明组网成功。
461
462### 3. 发起迁移
463
4641. 在B设备打开多设备协同权限的应用,A设备打开全局任务中心demo,A设备出现A设备名称(即:本机:OpenHarmony 3.2)和B设备名称(OpenHarmony 3.2)。
465   ![hop-cross-device-migration](figures/hop-cross-device-migration4.png)
4662. 点击B设备名称,然后出现B设备的应用。
467   ![hop-cross-device-migration](figures/hop-cross-device-migration5.png)
4683. 最后将应用拖拽到A设备名称处,A设备应用被拉起,B设备应用退出。
469   ![hop-cross-device-migration](figures/hop-cross-device-migration6.png)
470