hop-cross-device-migration.md 18.6 KB
Newer Older
H
hobbycao 已提交
1
# 跨端迁移
Z
zengyawen 已提交
2

L
liuxiaowei42 已提交
3
## 概述
Z
zengyawen 已提交
4

L
liuxiaowei42 已提交
5
在用户使用设备的过程中,当使用情境发生变化时(例如从室内走到户外或者周围有更适合的设备等),之前使用的设备可能已经不适合继续当前的任务,此时,用户可以选择新的设备来继续当前的任务,原设备可按需决定是否退出任务,这个就是跨端迁移的场景。常见的跨端迁移场景实例:在平板上播放的视频,迁移到智慧屏继续播放,从而获得更佳的观看体验;平板上的视频应用退出。在应用开发层面,跨端迁移指在A端运行的UIAbility迁移到B端上,完成迁移后,B端UIAbility继续任务,而A端UIAbility可按需决定是否退出。
Z
zengyawen 已提交
6

L
liuxiaowei42 已提交
7
跨端迁移的核心任务是将应用的当前状态(包括页面控件、状态变量等)无缝迁移到另一设备,从而在新设备上无缝接续应用体验。这意味着用户在一台设备上进行的操作可以在另一台设备的相同应用中快速切换并无缝衔接。
H
hobbycao 已提交
8

L
liuxiaowei42 已提交
9
主要功能包括:
Z
zengyawen 已提交
10

L
liuxiaowei42 已提交
11
- 支持用户自定义数据存储及恢复。
Z
zengyawen 已提交
12

L
liuxiaowei42 已提交
13
- 支持页面路由信息和页面控件状态数据的存储及恢复。
Z
zengyawen 已提交
14

L
liuxiaowei42 已提交
15
- 支持应用兼容性检测。
Z
zengyawen 已提交
16

L
liuxiaowei42 已提交
17
- 支持应用根据实际使用场景动态设置迁移状态(默认迁移状态为 **ACTIVE** 激活状态)。例如,编辑类应用在编辑文本的页面下才需要迁移,其他页面不需要迁移,则可以通过[`setMissionContinueState`](../reference/apis/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextsetmissioncontinuestate10)进行控制。
Z
zengyawen 已提交
18

L
liuxiaowei42 已提交
19
- 支持应用动态选择是否进行页面栈恢复(默认进行页面栈信息恢复)。例如,应用希望自定义迁移到其他设备后显示的页面,则可以通过[`SUPPORT_CONTINUE_PAGE_STACK_KEY`](../reference/apis/js-apis-app-ability-wantConstant.md#wantconstantparams)进行控制。
Z
zengyawen 已提交
20

L
liuxiaowei42 已提交
21
- 支持应用动态选择迁移成功后是否退出迁移源端应用(默认迁移成功后退出迁移源端应用)。可以通过[`SUPPORT_CONTINUE_SOURCE_EXIT_KEY`](../reference/apis/js-apis-app-ability-wantConstant.md#wantconstantparams)进行控制。
Z
zengyawen 已提交
22

L
liuxiaowei42 已提交
23 24 25
  > **说明:**
  >
  > 开发者可以开发具有迁移能力的应用,迁移的触发由系统应用完成。
Z
zengyawen 已提交
26 27


L
liuxiaowei42 已提交
28
## 运作机制
H
hobbycao 已提交
29

L
liuxiaowei42 已提交
30
![hop-cross-device-migration](figures/hop-cross-device-migration.png)
Z
zengyawen 已提交
31

L
liuxiaowei42 已提交
32 33 34
1. 在源端,通过`UIAbility`[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityoncontinue)回调,开发者可以保存待接续的业务数据。例如,在浏览器应用中完成跨端迁移,开发者需要使用[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityoncontinue)回调保存页面URL等业务内容,而系统将自动保存页面状态,如当前浏览进度。
2. 分布式框架提供了跨设备应用界面、页面栈以及业务数据的保存和恢复机制,它负责将数据从源端发送到对端。 
3. 在对端,同一`UIAbility`可以通过[`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)/[`onNewWant()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityonnewwant)接口来恢复业务数据。
Z
zengyawen 已提交
35 36


L
liuxiaowei42 已提交
37
## 约束限制
38

L
liuxiaowei42 已提交
39 40 41
- 跨端迁移要求在同一`UIAbility`之间进行,也就是需要相同的`bundleName``abilityName`和签名信息。
- 为了获得最佳体验,使用`wantParam`传输的数据需要控制在100KB以下。
- 当前部分ArkUI组件支持迁移后,将特定状态恢复到对端设备。详情请见[分布式迁移标识](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-restoreId.md)
42

L
liuxiaowei42 已提交
43
## 开发步骤
L
liuxiaowei42 已提交
44

L
liuxiaowei42 已提交
45
1. 需要申请`ohos.permission.DISTRIBUTED_DATASYNC`权限,配置方式请参见[配置文件权限声明](../security/accesstoken-guidelines.md#配置文件权限声明)
L
liuxiaowei42 已提交
46

L
liuxiaowei42 已提交
47
2. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/accesstoken-guidelines.md#向用户申请授权)
48

L
liuxiaowei42 已提交
49
3.[module.json5配置文件](../quick-start/module-configuration-file.md)的abilities标签中配置跨端迁移标签`continuable`
50

Z
zengyawen 已提交
51 52 53
   ```json
   {
     "module": {
54
       // ...
55 56
       "abilities": [
         {
57
           // ...
L
liuxiaowei42 已提交
58
           "continuable": true, // 配置UIAbility支持迁移
Z
zengyawen 已提交
59 60 61 62 63 64
         }
       ]
     }
   }
   ```

L
liuxiaowei42 已提交
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
   > **说明:**
   >
   > 根据需要配置应用启动模式类型,配置详情请参照[UIAbility组件启动模式](uiability-launch-type.md)。

4. 在源端`UIAbility`中实现[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityoncontinue)回调。

`UIAbility`实例触发迁移时,[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityoncontinue)回调在源端被调用,开发者可以在该接口中保存迁移数据,实现应用兼容性检测,决定是否支持此次迁移。

   - 保存迁移数据:开发者可以将要迁移的数据通过键值对的方式保存在`wantParam`参数中。
   - 应用兼容性检测:开发者可以通过从`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`获取到迁移对端应用的版本号与迁移源端应用版本号做兼容校验。
   - 迁移决策:开发者可以通过[`onContinue()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityoncontinue)回调的返回值决定是否支持此次迁移。

   ```ts
   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>):AbilityConstant.OnContinueResult {
       let version = wantParam.version;
       let targetDevice = wantParam.targetDevice;
       console.info(`onContinue version = ${version}, targetDevice: ${targetDevice}`); // 准备迁移数据
   
       // 获取源端版本号
       let versionSrc: number = -1; // 请填充具体获取版本号的代码
   
       // 兼容性校验
       if (version !== versionSrc) {
         // 在兼容性校验不通过时返回MISMATCH
         return AbilityConstant.OnContinueResult.MISMATCH;
       }
   
       // 将要迁移的数据保存在wantParam的自定义字段(例如data)中
       const continueInput = '迁移的数据';
       wantParam['data'] = continueInput;
   
       return AbilityConstant.OnContinueResult.AGREE;
     }
   }
   ```
L
liuxiaowei42 已提交
104

L
liuxiaowei42 已提交
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
5. 源端设备`UIAbility`实例在冷启动和热启动情况下分别会调用不同的接口来恢复数据和加载UI。  
   在对端设备的`UIAbility`中,需要实现[`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)/[`onNewWant()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityonnewwant)接口来恢复迁移数据。

   通过在[`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)/[`onNewWant()`](../reference/apis/js-apis-app-ability-uiAbility.md#abilityonnewwant)回调中检查`launchReason`,可以判断此次启动是否有迁移触发。开发者可以从`want`中获取之前保存的迁移数据,并在数据恢复后调用`restoreWindowStage()`来触发页面恢复,包括页面栈信息。

   ```ts
   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 {
       console.info('EntryAbility onCreate')
       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);
       }
     }
   
     onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
        console.info('EntryAbility onNewWant')
134 135 136 137 138 139 140 141 142
        if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
          // get user data from want params
          let continueInput = '';
          if (want.parameters != undefined) {
            continueInput = JSON.stringify(want.parameters.data);
            console.info(`continue input ${continueInput}`);
          }
          this.context.restoreWindowStage(this.storage);
        }
L
liuxiaowei42 已提交
143
      }
L
liuxiaowei42 已提交
144 145
   }
   ```
L
liuxiaowei42 已提交
146

L
liuxiaowei42 已提交
147
## 可选配置迁移能力
L
liuxiaowei42 已提交
148

L
liuxiaowei42 已提交
149
### 动态配置迁移能力
L
liuxiaowei42 已提交
150

L
liuxiaowei42 已提交
151
从API version 10开始,提供了支持动态配置迁移能力的功能。即应用可以根据实际使用场景,在需要迁移时开启应用迁移能力;在业务不需要迁移时则可以关闭迁移能力。
L
liuxiaowei42 已提交
152

L
liuxiaowei42 已提交
153
开发者可以通过调用[`setMissionContinueState()`](../reference/apis/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextsetmissioncontinuestate10)接口对迁移能力进行设置。默认状态下,应用的迁移能力为**ACTIVE**状态,即迁移能力开启,可以迁移。
L
liuxiaowei42 已提交
154 155 156 157 158

**设置迁移能力的时机**

迁移能力的改变可以根据实际业务需求和代码实现,发生在应用生命周期的绝大多数时机。本文介绍常用的几种配置方式。

L
liuxiaowei42 已提交
159
`UIAbility`[`onCreate()`](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)回调中调用接口,可以在应用创建时设置应用的迁移状态。
L
liuxiaowei42 已提交
160 161 162

```ts
// EntryAbility.ets
163 164 165
import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import Want from '@ohos.app.ability.Want';
L
liuxiaowei42 已提交
166

167 168 169 170 171 172 173 174
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)}`);
    });
    // ...
  }
L
liuxiaowei42 已提交
175 176 177
}
```

L
liuxiaowei42 已提交
178
在页面的`onPageShow()`回调中调用接口,可以设置单个页面出现时应用的迁移状态。
L
liuxiaowei42 已提交
179 180 181

```ts
// PageName.ets
182
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
L
liuxiaowei42 已提交
183
import common from '@ohos.app.ability.common';
184 185
@Entry
@Component
L
liuxiaowei42 已提交
186
struct PageName {
187 188 189
  private context = getContext(this) as common.UIAbilityContext;
  build() {
    // ...
L
liuxiaowei42 已提交
190
  }
191
  // ...
L
liuxiaowei42 已提交
192 193 194
  onPageShow(){
  // 进入该页面时,将应用设置为可迁移状态
    this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
L
liuxiaowei42 已提交
195
      console.info(`setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
196
    });
L
liuxiaowei42 已提交
197 198 199 200
  }
}
```

L
liuxiaowei42 已提交
201
在某个组件的触发事件中设置应用迁移能力。
L
liuxiaowei42 已提交
202 203

```ts
204 205
// PageName.ets
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
L
liuxiaowei42 已提交
206 207
import common from '@ohos.app.ability.common';

208 209 210 211 212 213 214
@Entry
@Component
struct PageName {
  private context = getContext(this) as common.UIAbilityContext;
  build() {
    // ...
    Button() {
L
liuxiaowei42 已提交
215
      // ...
216
    }.onClick(()=>{
L
liuxiaowei42 已提交
217
    // 点击该按钮时,将应用设置为可迁移状态
218
      this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
L
liuxiaowei42 已提交
219
        console.info(`setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
220 221 222
      });
    })
  }
L
liuxiaowei42 已提交
223 224 225 226 227
}
```

**保证迁移连续性**

L
liuxiaowei42 已提交
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**
L
liuxiaowei42 已提交
229 230 231

```ts
// EntryAbility.ets
232 233 234
import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import Want from '@ohos.app.ability.Want';
L
liuxiaowei42 已提交
235

236 237 238 239 240 241
export default class EntryAbility extends UIAbility {
  // ...
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // ...
    // 调用原因为迁移时,设置状态为可迁移,应对冷启动情况
    this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) => {
L
liuxiaowei42 已提交
242
        console.info(`setMissionContinueState INACTIVE result: ${JSON.stringify(result)}`);
243
    });
L
liuxiaowei42 已提交
244
  }
L
liuxiaowei42 已提交
245
  
246 247 248 249 250
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // ...
    // 调用原因为迁移时,设置状态为可迁移,应对热启动情况
    if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
      this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
L
liuxiaowei42 已提交
251
        console.info(`setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
252 253
      });
    }
L
liuxiaowei42 已提交
254
  }
255
  // ...
L
liuxiaowei42 已提交
256 257
}
```
258

L
liuxiaowei42 已提交
259
### 按需迁移页面栈
Z
zengyawen 已提交
260

L
liuxiaowei42 已提交
261
支持应用动态选择是否进行页面栈恢复(默认进行页面栈信息恢复)。如果应用不想使用系统默认恢复的页面栈,则可以设置不进行页面栈迁移,而需要在`onWindowStageRestore()`设置迁移后进入的页面,参数定义见[SUPPORT_CONTINUE_PAGE_STACK_KEY](../reference/apis/js-apis-app-ability-wantConstant.md#wantconstantparams)
262

L
liuxiaowei42 已提交
263
应用在源端的页面栈中存在Index和Second路由,而在对端恢复时不需要按照源端页面栈进行恢复,需要恢复到指定页面。
264

L
liuxiaowei42 已提交
265
例如,`UIAbility`迁移不需要自动迁移页面栈信息。
Z
zengyawen 已提交
266

L
liuxiaowei42 已提交
267 268 269 270 271 272
```ts
// 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';
L
liuxiaowei42 已提交
273

L
liuxiaowei42 已提交
274 275 276 277 278 279 280 281
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;
  }
L
liuxiaowei42 已提交
282

L
liuxiaowei42 已提交
283 284 285 286 287
  onWindowStageRestore(windowStage: window.WindowStage) {
      // 若不需要自动迁移页面栈信息,则需要在此处设置应用迁移后进入的页面
    windowStage.loadContent('pages/Index', (err, data) => {
      if (err.code) {
        return;
288
      }
L
liuxiaowei42 已提交
289 290 291 292
    });
  }
}
```
L
liuxiaowei42 已提交
293

L
liuxiaowei42 已提交
294
### 按需退出
L
liuxiaowei42 已提交
295

L
liuxiaowei42 已提交
296
支持应用动态选择迁移成功后是否退出迁移源端应用(默认迁移成功后退出迁移源端应用)。如果应用不想让系统自动退出迁移源端应用,则可以设置不退出,参数定义见[SUPPORT_CONTINUE_SOURCE_EXIT_KEY](../reference/apis/js-apis-app-ability-wantConstant.md#wantconstantparams)
L
liuxiaowei42 已提交
297

L
liuxiaowei42 已提交
298
示例:`UIAbility`设置迁移成功后,源端不需要退出迁移应用。
L
liuxiaowei42 已提交
299

L
liuxiaowei42 已提交
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
```ts
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;
  }
}
```
L
liuxiaowei42 已提交
315 316 317 318 319 320 321 322 323 324 325

## 验证指导

为方便开发者验证已开发的可迁移应用,当前OpenHarmony提供了一个全局任务中心demo作为迁移的入口。下面介绍通过安装全局任务中心来验证迁移的方式。

### 1. 编译安装全局任务中心

#### **配置环境**

public-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)

L
liuxiaowei42 已提交
326 327 328
> **说明**:
>
> 本文中的截图仅为参考,具体的显示界面请以实际使用的DevEco Studio和SDK的版本为准。
L
liuxiaowei42 已提交
329 330 331 332 333

#### **下载MissionCenter_Demo[示例代码](https://gitee.com/openharmony/ability_dmsfwk/tree/master/services/dtbschedmgr/test/missionCenterDemo/dmsDemo/entry/src/main)**

#### **编译工程文件**

L
liuxiaowei42 已提交
334 335
​	a.新建OpenHarmony 空的工程,找到对应的文件夹替换下载文件  
![hop-cross-device-migration](figures/hop-cross-device-migration1.png)
L
liuxiaowei42 已提交
336

L
liuxiaowei42 已提交
337
​	b.自动签名,编译安装。
L
liuxiaowei42 已提交
338 339 340

​		DevEco的自动签名模板默认签名权限为normal级。而本应用设计到ohos.permission.MANAGE_MISSIONS权限为system_core级别。自动生成的签名无法获得足够的权限,所以需要将权限升级为system_core级别,然后签名。

L
liuxiaowei42 已提交
341
​	c.系统权限设置(以api10目录为例): 将Sdk目录下的openharmony\api版本(如:10)\toolchains\lib\UnsgnedReleasedProfileTemplate.json文件中的"apl":"normal_core"改为"apl":"system_core"。
H
hobbycao 已提交
342

L
liuxiaowei42 已提交
343 344 345 346 347
1. 点击file->Project Structrue。  
   ![hop-cross-device-migration](figures/hop-cross-device-migration2.png)
2. 点击Signing Configs  点击OK。  
   ![hop-cross-device-migration](figures/hop-cross-device-migration3.png)
3. 连接开发板运行生成demo。
H
hobbycao 已提交
348

L
liuxiaowei42 已提交
349
### 2. 设备组网
H
hobbycao 已提交
350

L
liuxiaowei42 已提交
351 352 353 354 355
1. 打开A,B两设备的计算器。
2. 点击右上角箭头选择B设备。
3. 在B设备选择信任设备,弹出PIN码。
4. 在A设备输入PIN码。
5. 已组网成功,验证方法:在A设备输入数字,B设备同步出现则证明组网成功。
H
hobbycao 已提交
356

L
liuxiaowei42 已提交
357
### 3. 发起迁移
H
hobbycao 已提交
358

L
liuxiaowei42 已提交
359 360 361 362 363 364
1. 在B设备打开多设备协同权限的应用,A设备打开全局任务中心demo,A设备出现A设备名称(即:本机:OpenHarmony 3.2)和B设备名称(OpenHarmony 3.2)。  
   ![hop-cross-device-migration](figures/hop-cross-device-migration4.png)
2. 点击B设备名称,然后出现B设备的应用。  
   ![hop-cross-device-migration](figures/hop-cross-device-migration5.png)
3. 最后将应用拖拽到A设备名称处,A设备应用被拉起,B设备应用退出。  
   ![hop-cross-device-migration](figures/hop-cross-device-migration6.png)