• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Cross-Device Migration
2
3## Overview
4
5When the environment changes, for example, when a user goes outdoors or when a more appropriate device is detected, the user can migrate an ongoing task to another device for better experience. The application on the source device can automatically exit, depending on the setting. A typical cross-device migration scenario is as follows: The user migrates a video playback task from a tablet to a smart TV. The video application on the tablet exits. From the perspective of application development, cross-device migration enables the [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md) component running on device A to migrate to and keep running on device B. After the migration is complete, the UIAbility component on device A automatically exits (depending on the setting).
6
7Cross-device migration supports the following features:
8
9- Saving and restoring custom data
10
11- Saving and restoring page routing information and page component status data
12
13- Checking application compatibility
14
15- Dynamically setting the migration state (**ACTIVE** by default)
16
17  For example, for an editing application, only the text editing page needs to be migrated to the target device. In this case, you can call [setMissionContinueState](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextsetmissioncontinuestate10) for precise control.
18
19- Determining whether to restore the page stack (restored by default)
20
21  If an application wants to customize the page to be displayed after being migrated to the target device, you can use [SUPPORT_CONTINUE_PAGE_STACK_KEY](../reference/apis-ability-kit/js-apis-app-ability-wantConstant.md#params) for precise control.
22
23- Determining whether to exit the application on the source device after a successful migration (application exit by default)
24
25  You can use [SUPPORT_CONTINUE_SOURCE_EXIT_KEY](../reference/apis-ability-kit/js-apis-app-ability-wantConstant.md#params) for precise control.
26
27  > **NOTE**
28  >
29  > You only need to develop an application with the migration capabilities. System applications will trigger cross-device migration.
30
31## Working Principles
32
33![hop-cross-device-migration](figures/hop-cross-device-migration.png)
34
351. On the source device, the [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md) uses the [onContinue()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncontinue) callback to save service data to be migrated. For example, to complete cross-device migration, a browser application uses the **onContinue()** callback to save service data such as the page URL.
362. The distributed framework provides a mechanism for saving and restoring application page stacks and service data across devices. It sends the data from the source device to the target device.
373. On the target device, the same UIAbility uses the [onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncreate) callback (in the case of cold start) or [onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonnewwant) callback (in the case of hot start) to restore the service data.
38
39## Cross-Device Migration Process
40
41The following figure shows the cross-device migration process when a migration request is initiated from the migration entry of the peer device.
42
43![hop-cross-device-migration](figures/hop-cross-device-migration4.png)
44
45## Constraints
46
47- Cross-device migration must be performed between the same [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md) component. In other words, **bundleName**, **abilityName**, and **signature** of the component on the two devices must be the same.
48- For better user experience, the data to be transmitted via the **wantParam** parameter must be less than 100 KB.
49
50## How to Develop
51
521. Configure the **continuable** tag under **abilities** in the [module.json5 file](../quick-start/module-configuration-file.md).
53
54   ```json
55   {
56     "module": {
57       // ...
58       "abilities": [
59         {
60           // ...
61           "continuable": true, // Configure the UIAbility to support migration.
62         }
63       ]
64     }
65   }
66   ```
67
68   > **NOTE**
69   >
70   > Configure the application launch type. For details, see [UIAbility Launch Type](uiability-launch-type.md).
71
722. Implement **onContinue()** in the UIAbility on the source device.
73
74    When a migration is triggered for the [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md), [onContinue()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncontinue) is called on the source device. You can use either synchronous or asynchronous mode to save the data in this callback to implement application compatibility check and migration decision.
75
76    1. Saving data to migrate: You can save the data to migrate in key-value pairs in **wantParam**.
77
78    2. (Optional) Checking application compatibility: You can obtain the application version on the target device from **wantParam.version** in the **onContinue()** callback and compare it with the application version on the source device. If the version compatibility check fails, the application should notify users of the cause of the migration failure.
79
80       > **NOTE**
81       >
82       > If the compatibility issues have little or no impact on migration experience, you can skip this check.
83
84    3. Returning the migration result: You can determine whether migration is supported based on the return value of **onContinue()**. For details about the return values, see [AbilityConstant.OnContinueResult](../reference/apis-ability-kit/js-apis-app-ability-abilityConstant.md#oncontinueresult).
85
86
87    Certain fields (listed in the table below) in **wantParam** passed in to **onContinue()** are preconfigured in the system. You can use these fields for service processing. When defining custom `wantParam` data, do not use the same keys as the preconfigured ones to prevent data exceptions due to system overwrites.
88
89    | Field|Description|
90    | ---- | ---- |
91    | version | Version the application on the target device.|
92    | targetDevice | Network ID of the target device.|
93
94    ```ts
95    import { AbilityConstant, UIAbility } from '@kit.AbilityKit';
96    import { hilog } from '@kit.PerformanceAnalysisKit';
97    import { promptAction } from '@kit.ArkUI';
98
99    const TAG: string = '[MigrationAbility]';
100    const DOMAIN_NUMBER: number = 0xFF00;
101
102    export default class MigrationAbility extends UIAbility {
103      // Prepare data to migrate in onContinue.
104      onContinue(wantParam: Record<string, Object>):AbilityConstant.OnContinueResult {
105        let targetVersion = wantParam.version;
106        let targetDevice = wantParam.targetDevice;
107        hilog.info(DOMAIN_NUMBER, TAG, `onContinue version = ${targetVersion}, targetDevice: ${targetDevice}`);
108
109        // The application can set the minimum compatible version based on the source version, which can be obtained from the versionCode field in the app.json5 file. This is to prevent incompatibility caused because the target version is too earlier.
110    	let versionThreshold: number = -1; // Use the minimum version supported by the application.
111        // Compatibility verification
112        if (targetVersion < versionThreshold) {
113          // It is recommended that users be notified of the reason why the migration is rejected if the version compatibility check fails.
114          promptAction.showToast({
115              message: 'The target application version is too early to continue. Update the application and try again.',
116              duration: 2000
117          })
118          // Return MISMATCH when the compatibility check fails.
119          return AbilityConstant.OnContinueResult.MISMATCH;
120        }
121
122        // Save the data to migrate in the custom field (for example, data) of wantParam.
123        const continueInput = 'Data to migrate';
124        wantParam['data'] = continueInput;
125
126        return AbilityConstant.OnContinueResult.AGREE;
127      }
128    }
129    ```
130
1313. For the UIAbility on the target device, implement [onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncreate) or [onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonnewwant) to restore the data and load the UI.
132
133    The APIs to call vary according to the launch types, as shown below.
134
135    ![hop-cross-device-migration](figures/hop-cross-device-migration5.png)
136
137    > **NOTE**
138    >
139    > When an application is launched as a result of a migration, the [onWindowStageRestore()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonwindowstagerestore) lifecycle callback function, rather than [onWindowStageCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonwindowstagecreate), is triggered following **onCreate()** or **onNewWant()**. This sequence occurs for both cold and hot starts.
140    >
141    > If you have performed some necessary initialization operations during application launch in **onWindowStageCreate()**, you must perform the same initialization operations in **onWindowStageRestore()** after the migration to avoid application exceptions.
142
143    - The **launchReason** parameter in the **onCreate()** or **onNewWant()** callback specifies whether the launch is triggered as a result of a migration (whether the value is **CONTINUATION**).
144    - You can obtain the saved data from the [want](../reference/apis-ability-kit/js-apis-app-ability-want.md) parameter.
145    - To use system-level page stack restoration, you must call [restoreWindowStage()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextrestorewindowstage) to trigger page restoration with page stacks before **onCreate()** or **onNewWant()** is complete. For details, see [On-Demand Page Stack Migration](./hop-cross-device-migration.md#on-demand-page-stack-migration).
146
147    ```ts
148    import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
149    import { hilog } from '@kit.PerformanceAnalysisKit';
150
151    const TAG: string = '[MigrationAbility]';
152    const DOMAIN_NUMBER: number = 0xFF00;
153
154    export default class MigrationAbility extends UIAbility {
155      storage : LocalStorage = new LocalStorage();
156
157      onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
158        hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Ability onCreate');
159        if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
160          // Restore the saved data from want.parameters.
161          let continueInput = '';
162          if (want.parameters !== undefined) {
163            continueInput = JSON.stringify(want.parameters.data);
164            hilog.info(DOMAIN_NUMBER, TAG, `continue input ${JSON.stringify(continueInput)}`);
165          }
166          // Trigger page restoration.
167          this.context.restoreWindowStage(this.storage);
168        }
169      }
170
171      onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
172        hilog.info(DOMAIN_NUMBER, TAG, 'onNewWant');
173        if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
174          // Restore the saved data from want.parameters.
175          let continueInput = '';
176          if (want.parameters !== undefined) {
177            continueInput = JSON.stringify(want.parameters.data);
178            hilog.info(DOMAIN_NUMBER, TAG, `continue input ${JSON.stringify(continueInput)}`);
179          }
180          // Trigger page restoration.
181          this.context.restoreWindowStage(this.storage);
182        }
183      }
184    }
185    ```
186
187## Configuring Optional Migration Features
188
189### Dynamically Setting the Migration State
190
191Since API version 10, you can dynamically set the migration state. Specifically, you can enable or disable migration as required by calling [setMissionContinueState()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextsetmissioncontinuestate10). By default, **ACTIVE** is set for an application, indicating that migration is enabled.
192
193**Time for Setting the Migration State**
194
195To implement special scenarios, for example, where migration is required only for a specific page or upon a specific event, perform the following steps:
196
1971. In the [onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncreate) lifecycle callback of the [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md), disable cross-device migration.
198
199    ```ts
200    // MigrationAbility.ets
201    import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
202    import { hilog } from '@kit.PerformanceAnalysisKit';
203
204    const TAG: string = '[MigrationAbility]';
205    const DOMAIN_NUMBER: number = 0xFF00;
206
207    export default class MigrationAbility extends UIAbility {
208      onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
209        // ...
210        this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) => {
211          hilog.info(DOMAIN_NUMBER, TAG, `setMissionContinueState INACTIVE result: ${JSON.stringify(result)}`);
212        });
213        // ...
214      }
215    }
216    ```
217
218 2. To enable cross-device migration for a specific page, call the migration API in [onPageShow()](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#onpageshow) of the page.
219
220    ```ts
221    // Page_MigrationAbilityFirst.ets
222    import { AbilityConstant, common } from '@kit.AbilityKit';
223    import { hilog } from '@kit.PerformanceAnalysisKit';
224
225    const TAG: string = '[MigrationAbility]';
226    const DOMAIN_NUMBER: number = 0xFF00;
227
228    @Entry
229    @Component
230    struct Page_MigrationAbilityFirst {
231      private context = getContext(this) as common.UIAbilityContext;
232      build() {
233        // ...
234      }
235      // ...
236      onPageShow(){
237        // When the page is displayed, set the migration state to ACTIVE.
238        this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
239          hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
240        });
241      }
242    }
243    ```
244
2453. To enable cross-device migration for a specific event, call the migration API in the event. The following uses the [onClick()](../reference/apis-arkui/arkui-ts/ts-universal-events-click.md#onclick) event of the **Button** component as an example:
246
247    ```ts
248    // Page_MigrationAbilityFirst.ets
249    import { AbilityConstant, common } from '@kit.AbilityKit';
250    import { hilog } from '@kit.PerformanceAnalysisKit';
251    import { promptAction } from '@kit.ArkUI';
252
253    const TAG: string = '[MigrationAbility]';
254    const DOMAIN_NUMBER: number = 0xFF00;
255
256    @Entry
257    @Component
258    struct Page_MigrationAbilityFirst {
259      private context = getContext(this) as common.UIAbilityContext;
260      build() {
261        Column() {
262          //...
263          List({ initialIndex: 0 }) {
264            ListItem() {
265              Row() {
266                //...
267              }
268              .onClick(() => {
269                // When the button is clicked, set the migration state to ACTIVE.
270                this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
271                  hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
272                  promptAction.showToast({
273                    message: 'Success'
274                  });
275                });
276              })
277            }
278            //...
279          }
280          //...
281        }
282        //...
283      }
284    }
285    ```
286
287### Migrating the Page Stack on Demand
288
289
290> **NOTE**
291>
292> Currently, only the page stack implemented based on the router module can be automatically restored. The page stack implemented using the **Navigation** component cannot be automatically restored.
293>
294> If an application uses the **Navigation** component for routing, you can disable default page stack migration by setting [SUPPORT_CONTINUE_PAGE_STACK_KEY](../reference/apis-ability-kit/js-apis-app-ability-wantConstant.md#params) to **false**. In addition, save the page (or page stack) to be migrated in **want**, and manually load the specified page on the target device.
295
296By default, the page stack is restored during the migration of a [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md). Before [onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncreate) or [onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonnewwant) finishes the execution, call [restoreWindowStage()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#restore) to pass in the current window context, which will be used for page stack loading and restoration. The **restoreWindowStage()** API must be executed in a synchronous API. If it is executed during an asynchronous callback, there is a possibility that the page fails to be loaded during application startup.
297
298The following uses **onCreate()** as an example:
299
300```ts
301import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
302
303export default class MigrationAbility extends UIAbility {
304  storage : LocalStorage = new LocalStorage();
305
306  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
307      // ...
308      // Trigger page restoration before the synchronous API finishes the execution.
309      this.context.restoreWindowStage(this.storage);
310  }
311}
312```
313
314If the application does not want to use the page stack restored by the system, set [SUPPORT_CONTINUE_PAGE_STACK_KEY](../reference/apis-ability-kit/js-apis-app-ability-wantConstant.md#params) to **false**, and specify the page to be displayed after the migration in [onWindowStageRestore()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonwindowstagerestore). If the page to be displayed after the migration is not specified, a blank page will be displayed.
315
316For example, a UIAbility does not want to restore the page stack displayed on the source device. Instead, it wants **Page_MigrationAbilityThird** to be restored after the migration.
317
318```ts
319// MigrationAbility.ets
320import { AbilityConstant, UIAbility, wantConstant } from '@kit.AbilityKit';
321import { hilog } from '@kit.PerformanceAnalysisKit';
322import { window } from '@kit.ArkUI';
323
324const TAG: string = '[MigrationAbility]';
325const DOMAIN_NUMBER: number = 0xFF00;
326
327export default class MigrationAbility extends UIAbility {
328  onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
329    // ...
330    // Configure not to use the system page stack during restoration.
331    wantParam[wantConstant.Params.SUPPORT_CONTINUE_PAGE_STACK_KEY] = false;
332    return AbilityConstant.OnContinueResult.AGREE;
333  }
334
335  onWindowStageRestore(windowStage: window.WindowStage): void {
336    // If the system page stack is not used for restoration, specify the page to be displayed after the migration.
337    windowStage.loadContent('pages/page_migrationability/Page_MigrationAbilityThird', (err, data) => {
338      if (err.code) {
339        hilog.error(DOMAIN_NUMBER, TAG, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
340        return;
341      }
342    });
343  }
344}
345```
346
347### Exiting the Application on Demand
348
349By default, the application on the source device exits after the migration is complete. If you want the application to continue running on the source device after the migration or perform other operations (such as saving drafts and clearing resources) before exiting on the source device, you can set [SUPPORT_CONTINUE_SOURCE_EXIT_KEY](../reference/apis-ability-kit/js-apis-app-ability-wantConstant.md#params) to **false**.
350
351Example: A UIAbility on the source device does not exit after a successful migration.
352
353```ts
354import { AbilityConstant, UIAbility, wantConstant } from '@kit.AbilityKit';
355import { hilog } from '@kit.PerformanceAnalysisKit';
356
357const TAG: string = '[MigrationAbility]';
358const DOMAIN_NUMBER: number = 0xFF00;
359
360export default class MigrationAbility extends UIAbility {
361  // ...
362  onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
363    hilog.info(DOMAIN_NUMBER, TAG, `onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`);
364    wantParam[wantConstant.Params.SUPPORT_CONTINUE_SOURCE_EXIT_KEY] = false;
365    return AbilityConstant.OnContinueResult.AGREE;
366  }
367}
368```
369
370### Migrating Between Abilities in the Same Application Across Devices
371Generally, the same ability is involved during a cross-device migration. However, different ability names may be configured for the same service on different device types, resulting in different abilities. To support migration in this scenario, you can configure the **continueType** flag under **abilities** in the [module.json5](../quick-start/module-configuration-file.md) file for association.
372
373The values of the **continueType** tag of the two abilities must be the same. The following is an example:
374   > **NOTE**
375   >
376   > The value of **continueType** must be unique in an application. The value is a string of a maximum of 127 bytes consisting of letters, digits, and underscores (_).
377   >
378   > The **continueType** tag is a string array. If multiple fields are configured, only the first field takes effect.
379
380```json
381   // Device A
382   {
383     "module": {
384       // ...
385       "abilities": [
386         {
387           // ...
388           "name": "Ability-deviceA",
389           "continueType": ['continueType1'], // Configuration of the continueType tag.
390         }
391       ]
392     }
393   }
394
395   // Device B
396   {
397     "module": {
398       // ...
399       "abilities": [
400         {
401           // ...
402           "name": "Ability-deviceB",
403           "continueType": ['continueType1'], // Same as the value of continueType on device A.
404         }
405       ]
406     }
407   }
408```
409
410### Migrating Abilities with Different Bundle Names in the Same Application Across Devices
411An application may use different bundle names on different devices. To support migration in this scenario, configure **abilities** in the **module.json5** file of the application as follows:
412
413- **continueBundleName**: bundle name of the application on the peer device.
414- **continueType**: The same value must be used.
415
416   > **NOTE**
417   >
418   > The value of **continueType** must be unique in an application. The value is a string of a maximum of 127 bytes consisting of letters, digits, and underscores (_).
419   >
420   > The **continueType** tag is a string array. If multiple fields are configured, only the first field takes effect.
421
422
423An example is as follows:
424
425An application with different bundle names is migrated between device A and device B. The bundle name of the application on device A is com.demo.example1, and that on device B is com.demo.example2.
426
427```JSON
428// In the configuration file for device A, set continueBundleName to the bundle name of the application on device B.
429{
430  "module": {
431    // ···
432    "abilities": [
433      {
434        "name": "EntryAbility",
435        // ···
436        "continueType": ["continueType"],
437        "continueBundleName": ["com.demo.example2"], // continueBundleName is set to com.demo.example2, which is the bundle name of the application on device B.
438
439      }
440    ]
441
442  }
443}
444```
445
446```JSON
447// In the configuration file for device B, set continueBundleName to the bundle name of the application on device A.
448{
449  "module": {
450    // ···
451    "abilities": [
452      {
453        "name": "EntryAbility",
454        // ···
455        "continueType": ["continueType"],
456        "continueBundleName": ["com.demo.example1"], // continueBundleName is set to com.demo.example1, which is the bundle name of the application on device A.
457
458      }
459    ]
460
461  }
462}
463
464```
465
466### Quickly Starting a Target Application
467By default, the target application on the peer device is not started immediately when the migration is initiated. It waits until the data to migrate is synchronized from the source device to the peer device. To start the target application immediately upon the initiation of a migration, you can add the **_ContinueQuickStart** suffix to the value of **continueType**. In this way, only the migrated data is restored after the data synchronization, delivering an even better migration experience.
468
469   ```json
470   {
471     "module": {
472       // ...
473       "abilities": [
474         {
475           // ...
476           "name": "EntryAbility"
477           "continueType": ['EntryAbility_ContinueQuickStart'], // If the continueType tag is configured, add the suffix '_ContinueQuickStart' to its value. If the continueType tag is not configured, you can use AbilityName + '_ContinueQuickStart' as the value of continueType to implement quick startup of the target application.
478         }
479       ]
480     }
481   }
482   ```
483With quick start, the target application starts while waiting for the data to migration, minimizing the duration that users wait for the migration to complete. Note that, for the first migration with quick start enabled, the [onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncreate) or [onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonnewwant) callback is triggered, in which **launchReason** is **PREPARE_CONTINUATION**. The introduction of the **launchReason** parameter solves problems related to redirection and timing. It also provides a loading screen during quick startup.
484
485Since API version 16, an application that displays a loading page during quick launch can [obtain the quick startup result during cross-device migration](../reference/apis-ability-kit/js-apis-app-ability-continueManager.md#continuemanageron). Depending on this result, the application can take appropriate actions. For example, if the quick startup is successful, the application can dismiss the loading page and proceed to the continuation page.
486
487The following figure shows the quick start process.
488
489![hop-cross-device-migration](figures/continue_quick_start.png)
490
491For an application configured with quick startup, two start requests are received for a migration. The differences are as follows:
492
493| Scenario          | Lifecycle Function                               | launchParam.launchReason                          |
494| -------------- | ------------------------------------------- | ------------------------------------------------- |
495| First start request| onCreate (in the case of cold start)<br>or onNewWant (in the case of hot start)| AbilityConstant.LaunchReason.PREPARE_CONTINUATION |
496| Second start request| onNewWant                                   | AbilityConstant.LaunchReason.CONTINUATION         |
497
498If fast start is not configured, only one start request is received.
499
500| Scenario        | Lifecycle Function                               | launchParam.launchReason                  |
501| ------------ | ------------------------------------------- | ----------------------------------------- |
502| One start request| onCreate (in the case of cold start)<br>or onNewWant (in the case of hot start)| AbilityConstant.LaunchReason.CONTINUATION |
503
504When quick start is configured, you also need to implement [onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncreate) or [onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonnewwant). The following is an example:
505
506```ts
507import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
508import { hilog } from '@kit.PerformanceAnalysisKit';
509
510const TAG: string = '[MigrationAbility]';
511const DOMAIN_NUMBER: number = 0xFF00;
512
513export default class MigrationAbility extends UIAbility {
514  storage : LocalStorage = new LocalStorage();
515
516  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
517    hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Ability onCreate');
518
519    // 1. Quick start is configured. Trigger the lifecycle callback when the application is launched immediately.
520    if (launchParam.launchReason === AbilityConstant.LaunchReason.PREPARE_CONTINUATION) {
521      // If the application data to migrate is large, add a loading screen here (for example, displaying "loading" on the screen).
522      // Handle issues related to custom redirection and timing.
523      // ...
524    }
525  }
526
527  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
528    hilog.info(DOMAIN_NUMBER, TAG, 'onNewWant');
529
530    // 1. Quick start is configured. Trigger the lifecycle callback when the application is launched immediately.
531    if (launchParam.launchReason === AbilityConstant.LaunchReason.PREPARE_CONTINUATION) {
532      // If the application data to migrate is large, add a loading screen here (for example, displaying "loading" on the screen).
533      // Handle issues related to custom redirection and timing.
534      // ...
535    }
536
537    // 2. Trigger the lifecycle callback when the migration data is restored.
538    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
539      // Restore the saved data from want.parameters.
540      let continueInput = '';
541      if (want.parameters !== undefined) {
542        continueInput = JSON.stringify(want.parameters.data);
543        hilog.info(DOMAIN_NUMBER, TAG, `continue input ${JSON.stringify(continueInput)}`);
544      }
545      // Trigger page restoration.
546      this.context.restoreWindowStage(this.storage);
547    }
548  }
549}
550```
551
552When the target application is quickly started, the [onWindowStageCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonwindowstagecreate) and [onWindowStageRestore()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonwindowstagerestore) callbacks are triggered in sequence. Generally, in **onWindowStageCreate()**, you call [loadContent()](../reference/apis-arkui/js-apis-window.md#loadcontent9) to load the page. This API throws an asynchronous task to load the home page. This asynchronous task is not synchronous with **onWindowStageRestore()**. If UI-related APIs (such as route APIs) are used in **onWindowStageRestore()**, the invoking time may be earlier than the home page loading time. To ensure the normal loading sequence, you can use [setTimeout()](../reference/common/js-apis-timer.md#settimeout) to throw an asynchronous task for related operations.
553
554The sample code is as follows:
555
556```ts
557import { UIAbility } from '@kit.AbilityKit';
558import { hilog } from '@kit.PerformanceAnalysisKit';
559import { UIContext, window } from '@kit.ArkUI';
560
561export default class EntryAbility extends UIAbility {
562  private uiContext: UIContext | undefined = undefined;
563
564  // ...
565
566  onWindowStageCreate(windowStage: window.WindowStage): void {
567    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
568
569    windowStage.loadContent('pages/Index', (err) => {
570      if (err.code) {
571        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
572        return;
573      }
574      this.uiContext = windowStage.getMainWindowSync().getUIContext();
575      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
576    });
577  }
578
579  onWindowStageRestore(windowStage: window.WindowStage): void {
580    setTimeout(() => {
581      // Throw the asynchronous task execution route to ensure that the task is executed after the home page is loaded.
582      this.uiContext?.getRouter().pushUrl({
583        url: 'pages/examplePage'
584      });
585    }, 0);
586  }
587
588  // ...
589}
590```
591
592## Cross-Device Data Migration
593
594Two data migration modes are provided. You can select either of them as required.
595  > **NOTE**
596  >
597  > Through the configuration of **restoreId**, certain ArkUI components can be restored to a given state on the target device after migration. For details, see [restoreId](../../application-dev/reference/apis-arkui/arkui-ts/ts-universal-attributes-restoreId.md).
598  >
599  > If distributed data objects need to be migrated, you must perform the following operations (required only in API version 11 and earlier versions):
600  >
601  > 1. Declare the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Declaring Permissions](../security/AccessToken/declare-permissions.md).
602  >
603  > 2. Display a dialog box to ask for authorization from the user when the application is started for the first time. For details, see [Requesting User Authorization](../security/AccessToken/request-user-authorization.md).
604
605### Using wantParam for Data Migration
606
607If the size of the data to migrate is less than 100 KB, you can add fields to **wantParam** to migrate the data. An example is as follows:
608
609```ts
610import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
611import { hilog } from '@kit.PerformanceAnalysisKit';
612
613const TAG: string = '[MigrationAbility]';
614const DOMAIN_NUMBER: number = 0xFF00;
615
616export default class MigrationAbility extends UIAbility {
617  storage: LocalStorage = new LocalStorage();
618
619  // Save the data on the source device.
620  onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
621    // Save the data to migrate in the custom field (for example, data) of wantParam.
622    const continueInput = 'Data to migrate';
623    wantParam['data'] = continueInput;
624    return AbilityConstant.OnContinueResult.AGREE;
625  }
626
627  // Restore the data on the target device.
628  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
629    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
630      // Obtain the data saved.
631      let continueInput = '';
632      if (want.parameters !== undefined) {
633        continueInput = JSON.stringify(want.parameters.data);
634        hilog.info(DOMAIN_NUMBER, TAG, `continue input ${continueInput}`);
635      }
636      // Trigger page restoration.
637      this.context.restoreWindowStage(this.storage);
638    }
639  }
640
641  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
642    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
643      let continueInput = '';
644      if (want.parameters !== undefined) {
645        continueInput = JSON.stringify(want.parameters.data);
646        hilog.info(DOMAIN_NUMBER, TAG, `continue input ${JSON.stringify(continueInput)}`);
647      }
648      // Trigger page restoration.
649      this.context.restoreWindowStage(this.storage);
650    }
651  }
652}
653```
654
655### Using Distributed Data Objects for Data Migration
656
657If the size of the data to migrate is greater than 100 KB or a file needs to be migrated, you can use a [distributed data object](../reference/apis-arkdata/js-apis-data-distributedobject.md) to implement data migration. For details, see [Cross-Device Synchronization of Distributed Data Objects](../database/data-sync-of-distributed-data-object.md).
658
659  > **NOTE**
660  >
661  > Since API version 12, it is difficult to obtain the file synchronization completion time when [cross-device file access](../file-management/file-access-across-devices.md) is used to migrate a file. To ensure a higher success rate, you are advised to use distributed data objects to carry assets. File migration implemented through cross-device file access still takes effect.
662
663#### Basic Data Migration
664
665To use a distributed data object, you must save data in the [onContinue()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncontinue) API on the source device and restore data in the [onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncreate) or [onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonnewwant) API of the target device.
666
667On the source device, save the data to migrate to a distributed [data object](../reference/apis-arkdata/js-apis-data-distributedobject.md#dataobject).
668
669- Use [create()](../reference/apis-arkdata/js-apis-data-distributedobject.md#distributeddataobjectcreate9) in **onContinue()** to create a distributed data object and add the data to migrate to the object.
670- Use [genSessionId()](../reference/apis-arkdata/js-apis-data-distributedobject.md#distributeddataobjectgensessionid) to generate a data object network ID, use the ID to call [setSessionId()](../reference/apis-arkdata/js-apis-data-distributedobject.md#setsessionid9) to add the data object to the network, and activate the distributed data object.
671- Use [save()](../reference/apis-arkdata/js-apis-data-distributedobject.md#save9) to persist the activated distributed data object to ensure that the peer device can still obtain data after the application exits from the source device.
672- The generated session ID is transferred to the peer device through **want** for activation and synchronization purposes.
673
674> **NOTE**
675>
676> Distributed data objects must be activated before being made persistent. Therefore, the **save()** API must be called after **setSessionId()**.
677>
678> For applications that need to exit from the source device after migration, use **await** to wait until the **save()** API finishes execution. This prevents the application from exiting before data is saved. Since API version 12, an asynchronous **onContinue()** API is provided for this scenario.
679>
680> Currently, the **sessionId** field in **wantParams** is occupied by the system in the migration process. You are advised to define another key in **wantParams** to store the ID to avoid data exceptions.
681
682The sample code is as follows:
683
684```ts
685// Import the modules.
686import { distributedDataObject } from '@kit.ArkData';
687import { UIAbility, AbilityConstant } from '@kit.AbilityKit';
688import { BusinessError } from '@kit.BasicServicesKit';
689import { hilog } from '@kit.PerformanceAnalysisKit';
690
691const TAG: string = '[MigrationAbility]';
692const DOMAIN_NUMBER: number = 0xFF00;
693
694// Define service data.
695class ParentObject {
696  mother: string
697  father: string
698
699  constructor(mother: string, father: string) {
700    this.mother = mother
701    this.father = father
702  }
703}
704
705// Strings, digits, Boolean values, and objects can be transferred.
706class SourceObject {
707  name: string | undefined
708  age: number | undefined
709  isVis: boolean | undefined
710  parent: ParentObject | undefined
711
712  constructor(name: string | undefined, age: number | undefined, isVis: boolean | undefined, parent: ParentObject | undefined) {
713    this.name = name
714    this.age = age
715    this.isVis = isVis
716    this.parent = parent
717  }
718}
719
720export default class MigrationAbility extends UIAbility {
721  d_object?: distributedDataObject.DataObject;
722
723  async onContinue(wantParam: Record<string, Object>): Promise<AbilityConstant.OnContinueResult> {
724    // ...
725    let parentSource: ParentObject = new ParentObject('jack mom', 'jack Dad');
726    let source: SourceObject = new SourceObject("jack", 18, false, parentSource);
727
728    // Create a distributed data object.
729    this.d_object = distributedDataObject.create(this.context, source);
730
731    // Generate a data object network ID and activate the distributed data object.
732    let dataSessionId: string = distributedDataObject.genSessionId();
733    this.d_object.setSessionId(dataSessionId);
734
735    // Transfer the network ID to the peer device.
736    wantParam['dataSessionId'] = dataSessionId;
737
738    // Persist the data object to ensure that the peer device can restore the data object even if the application exits after the migration.
739    // Obtain the network ID of the peer device from wantParam.targetDevice as an input parameter.
740    await this.d_object.save(wantParam.targetDevice as string).then((result:
741      distributedDataObject.SaveSuccessResponse) => {
742      hilog.info(DOMAIN_NUMBER, TAG, `Succeeded in saving. SessionId: ${result.sessionId}`,
743        `version:${result.version}, deviceId:${result.deviceId}`);
744    }).catch((err: BusinessError) => {
745      hilog.error(DOMAIN_NUMBER, TAG, 'Failed to save. Error: ', JSON.stringify(err) ?? '');
746    });
747
748    return AbilityConstant.OnContinueResult.AGREE;
749  }
750}
751```
752
753In [onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncreate) or [onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonnewwant), the peer device adds the same distributed data object as the source device for networking, in order to restore data.
754
755- Create an empty distributed data object to receive restored data.
756- Read the network ID of the distributed data object from [want](../reference/apis-ability-kit/js-apis-app-ability-want.md).
757- Register [on()](../reference/apis-arkdata/js-apis-data-distributedobject.md#onstatus9) to listen for data changes. In the event callback indicating that **status** is **restore**, implement the service operations that need to be performed when the data restoration is complete.
758- Call [setSessionId()](../reference/apis-arkdata/js-apis-data-distributedobject.md#setsessionid9) to add the data object to the network, and activate the distributed data object.
759
760> **NOTE**
761>
762> The distributed data object of the peer device to be added to the network cannot be a temporary variable. This is because the callback of the **on()** API may be executed after **onCreate()** or **onNewWant()** finishes execution. If the temporary variable is released, a null pointer exception may occur. You can use a class member variable to avoid this problem.
763>
764> The attributes of the object used to create the distributed data object on the peer device must be undefined before the distributed data object is activated. Otherwise, the source data will be overwritten after new data is added to the network, and data restoration will fail.
765>
766> Before activating the distributed data object, call **on()** to listen for the restore event. This helps prevent data restoration failure caused by event missing.
767
768The sample code is as follows:
769
770```ts
771import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
772import { distributedDataObject } from '@kit.ArkData';
773import { hilog } from '@kit.PerformanceAnalysisKit';
774
775const TAG: string = '[MigrationAbility]';
776const DOMAIN_NUMBER: number = 0xFF00;
777
778// The definition of the example data object is the same as above.
779export default class MigrationAbility extends UIAbility {
780  d_object?: distributedDataObject.DataObject;
781
782  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
783    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
784      // ...
785      // Call the encapsulated distributed data object processing function.
786      this.handleDistributedData(want);
787    }
788  }
789
790  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
791    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
792      if (want.parameters !== undefined) {
793        // ...
794        // Call the encapsulated distributed data object processing function.
795        this.handleDistributedData(want);
796      }
797    }
798  }
799
800  handleDistributedData(want: Want) {
801    // Create an empty distributed data object.
802    let remoteSource: SourceObject = new SourceObject(undefined, undefined, undefined, undefined);
803    this.d_object = distributedDataObject.create(this.context, remoteSource);
804
805    // Read the network ID of the distributed data object.
806    let dataSessionId = '';
807    if (want.parameters !== undefined) {
808      dataSessionId = want.parameters.dataSessionId as string;
809    }
810
811    // Add a data change listener.
812    this.d_object.on("status", (sessionId: string, networkId: string, status: 'online' | 'offline' | 'restored') => {
813      hilog.info(DOMAIN_NUMBER, TAG, "status changed " + sessionId + " " + status + " " + networkId);
814      if (status == 'restored') {
815        if (this.d_object) {
816          // Read data from the distributed data object when the restore status is received.
817          hilog.info(DOMAIN_NUMBER, TAG, "restored name:" + this.d_object['name']);
818          hilog.info(DOMAIN_NUMBER, TAG, "restored parents:" + JSON.stringify(this.d_object['parent']));
819        }
820      }
821    });
822
823    // Activate the distributed data object.
824    this.d_object.setSessionId(dataSessionId);
825  }
826}
827```
828
829#### File Migration
830
831A file, such as an image or document, must be converted to the [commonType.Asset](../reference/apis-arkdata/js-apis-data-commonType.md#asset) type before being encapsulated into a distributed data objects for migration. The migration implementation is similar to that of a common distributed data object. The following describes only the differences.
832
833On the source device, save the file asset to migrate to a distributed [data object](../reference/apis-arkdata/js-apis-data-distributedobject.md#dataobject).
834
835- Copy the file asset to the [distributed file directory](application-context-stage.md#obtaining-application-file-paths). For details about related APIs and usage, see [basic file APIs](../file-management/app-file-access.md).
836- Use the file in the distributed file directory to create an asset object.
837- Save the asset object as the root attribute of the distributed data object.
838
839Then, add the data object to the network and persist it. The procedure is the same as the migration of a common data object on the source device.
840
841An example is as follows:
842
843```ts
844// Import the modules.
845import { UIAbility, AbilityConstant } from '@kit.AbilityKit';
846import { distributedDataObject, commonType } from '@kit.ArkData';
847import { fileIo, fileUri } from '@kit.CoreFileKit';
848import { hilog } from '@kit.PerformanceAnalysisKit';
849import { BusinessError } from '@ohos.base';
850
851const TAG: string = '[MigrationAbility]';
852const DOMAIN_NUMBER: number = 0xFF00;
853
854// Define a data object.
855class ParentObject {
856  mother: string
857  father: string
858
859  constructor(mother: string, father: string) {
860    this.mother = mother
861    this.father = father
862  }
863}
864
865class SourceObject {
866  name: string | undefined
867  age: number | undefined
868  isVis: boolean | undefined
869  parent: ParentObject | undefined
870  attachment: commonType.Asset | undefined // New asset attribute.
871
872  constructor(name: string | undefined, age: number | undefined, isVis: boolean | undefined,
873              parent: ParentObject | undefined, attachment: commonType.Asset | undefined) {
874    this.name = name
875    this.age = age
876    this.isVis = isVis
877    this.parent = parent
878    this.attachment = attachment;
879  }
880}
881
882export default class MigrationAbility extends UIAbility {
883  d_object?: distributedDataObject.DataObject;
884
885  async onContinue(wantParam: Record<string, Object>): Promise<AbilityConstant.OnContinueResult> {
886    // ...
887
888    // 1. Write the asset to the distributed file directory.
889    let distributedDir: string = this.context.distributedFilesDir; // Obtain the distributed file directory.
890    let fileName: string = '/test.txt'; // File name.
891    let filePath: string = distributedDir + fileName; // File path.
892
893    try {
894      // Create a file in the distributed directory.
895      let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
896      hilog.info(DOMAIN_NUMBER, TAG, 'Create file success.');
897      // Write content to the file. (If the asset is an image, convert the image into a buffer and write the buffer.)
898      fileIo.writeSync(file.fd, '[Sample] Insert file content here.');
899      // Close the file.
900      fileIo.closeSync(file.fd);
901    } catch (error) {
902      let err: BusinessError = error as BusinessError;
903      hilog.error(DOMAIN_NUMBER, TAG, `Failed to openSync / writeSync / closeSync. Code: ${err.code}, message: ${err.message}`);
904    }
905
906    // 2. Use the file in the distributed file directory to create an asset object.
907    let distributedUri: string = fileUri.getUriFromPath(filePath); // Obtain the URI of the distributed file.
908
909    // Obtain file parameters.
910    let ctime: string = '';
911    let mtime: string = '';
912    let size: string = '';
913    await fileIo.stat(filePath).then((stat: fileIo.Stat) => {
914      ctime = stat.ctime.toString(); // Creation time
915      mtime = stat.mtime.toString(); // Modification time
916      size = stat.size.toString(); // File size
917    })
918
919    // Create an asset object.
920    let attachment: commonType.Asset = {
921      name: fileName,
922      uri: distributedUri,
923      path: filePath,
924      createTime: ctime,
925      modifyTime: mtime,
926      size: size,
927    }
928
929    // 3. Use the asset object as the root attribute of the distributed data object to create a distributed data object.
930    let parentSource: ParentObject = new ParentObject('jack mom', 'jack Dad');
931    let source: SourceObject = new SourceObject("jack", 18, false, parentSource, attachment);
932    this.d_object = distributedDataObject.create(this.context, source);
933
934    // Generate a network ID, activate the distributed data object, and save the data persistently.
935    // ...
936
937    return AbilityConstant.OnContinueResult.AGREE;
938  }
939}
940```
941
942The target application must create an asset object whose attributes are empty as the root attribute of the distributed data object. When the [on()](../reference/apis-arkdata/js-apis-data-distributedobject.md#onstatus9) event callback in which **status** is **restored** is received, the data synchronization including the asset is complete. The asset object of source application can be obtained in the same way as the basic data.
943
944> **NOTE**
945>
946> When the target application creates the distributed data object, the assets in the **SourceObject** object cannot be directly initialized using **undefined**. You need to create an asset object whose initial values of all attributes are empty so that the distributed object can identify the asset type.
947
948```ts
949import { UIAbility, Want } from '@kit.AbilityKit';
950import { distributedDataObject, commonType } from '@kit.ArkData';
951import { hilog } from '@kit.PerformanceAnalysisKit';
952
953const TAG: string = '[MigrationAbility]';
954const DOMAIN_NUMBER: number = 0xFF00;
955
956export default class MigrationAbility extends UIAbility {
957  d_object?: distributedDataObject.DataObject;
958
959  handleDistributedData(want: Want) {
960    // ...
961    // Create an asset object whose attributes are empty.
962    let attachment: commonType.Asset = {
963      name: '',
964      uri: '',
965      path: '',
966      createTime: '',
967      modifyTime: '',
968      size: '',
969    }
970
971    // Use the empty asset object to create a distributed data object. Other basic attributes can be directly set to undefined.
972    let source: SourceObject = new SourceObject(undefined, undefined, undefined, undefined, attachment);
973    this.d_object = distributedDataObject.create(this.context, source);
974
975    this.d_object.on("status", (sessionId: string, networkId: string, status: 'online' | 'offline' | 'restored') => {
976      if (status == 'restored') {
977        if (this.d_object) {
978          // The restored event callback is received, indicating that the synchronization of the distributed asset object is complete.
979          hilog.info(DOMAIN_NUMBER, TAG, "restored attachment:" + JSON.stringify(this.d_object['attachment']));
980        }
981      }
982    });
983    // ...
984  }
985}
986```
987
988If you want to synchronize multiple assets, use either of the following methods:
989
990- Method 1: Implement each asset as a root attribute of the distributed data object. This applies to scenarios where the number of assets to migrate is fixed.
991- Method 2: Transfer the asset array as an object. This applies to scenarios where the number of assets to migrate changes dynamically (for example, a user selects a variable number of images). Currently, the asset array cannot be directly transferred as the root attribute.
992
993To implement method 1, you can add more assets by referring to the method of adding an asset. To implement method 2, refer to the code snippet below:
994
995```ts
996// Import the modules.
997import { distributedDataObject, commonType } from '@kit.ArkData';
998import { UIAbility } from '@kit.AbilityKit';
999
1000// Define a data object.
1001class SourceObject {
1002  name: string | undefined
1003  assets: Object | undefined // Add an object attribute to the distributed data object.
1004
1005  constructor(name: string | undefined, assets: Object | undefined) {
1006    this.name = name
1007    this.assets = assets;
1008  }
1009}
1010
1011export default class MigrationAbility extends UIAbility {
1012  d_object?: distributedDataObject.DataObject;
1013
1014  // This API is used to convert an asset array into a record.
1015  GetAssetsWapper(assets: commonType.Assets): Record<string, commonType.Asset> {
1016    let wrapper: Record<string, commonType.Asset> = {}
1017    let num: number = assets.length;
1018    for (let i: number = 0; i < num; i++) {
1019      wrapper[`asset${i}`] = assets[i];
1020    }
1021    return wrapper;
1022  }
1023
1024  async onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
1025    // ...
1026
1027    // Create multiple asset objects.
1028    let attachment1: commonType.Asset = {
1029      // ...
1030    }
1031
1032    let attachment2: commonType.Asset = {
1033      // ...
1034    }
1035
1036    // Insert the asset objects into the asset array.
1037    let assets: commonType.Assets = [];
1038    assets.push(attachment1);
1039    assets.push(attachment2);
1040
1041    // Convert the asset array into a record object and use it to create a distributed data object.
1042    let assetsWrapper: Object = this.GetAssetsWapper(assets);
1043    let source: SourceObject = new SourceObject("jack", assetsWrapper);
1044    this.d_object = distributedDataObject.create(this.context, source);
1045
1046    // ...
1047  }
1048}
1049```
1050
1051## Verification Guide
1052
1053A mission center demo is provided for you to verify the migration capability of your application. The following walks you through on how to verify migration by using the demo.
1054
1055> **NOTE**
1056>
1057> The screenshots in this section are for reference only. The DevEco Studio and SDK versions in use prevail.
1058
1059**Compiling and Installing the Demo**
1060
10611. [Switch to the full SDK](../faqs/full-sdk-switch-guide.md) to compile and install the mission center.
1062
10632. Download the sample code of the [mission center demo](https://gitee.com/openharmony/ability_dmsfwk/tree/master/services/dtbschedmgr/test/missionCenterDemo/dmsDemo/entry/src/main).
1064
10653. Build the project file.
1066
1067    1. Create an empty project and replace the corresponding folders with the downloaded files.
1068
1069        ![hop-cross-device-migration](figures/hop-cross-device-migration1.png)
1070
1071    2. Complete the signature, build, and installation.
1072        ​The default signature permission provided by the automatic signature template of DevEco Studio is normal. The mission center demo requires the **ohos.permission.MANAGE_MISSIONS** permission, which is at the system_core level. Therefore, you must escalate the permission to the system_core level.
1073           1. Change **"apl":"normal"** to **"apl":"system_core"** in the **UnsignedReleasedProfileTemplate.json** file in **openharmony\apiVersion\toolchains\lib**, where apiVersion is a digit, for example, **10**.
1074
1075           2. Choose **File > Project Structure**.
1076
1077           ![hop-cross-device-migration](figures/hop-cross-device-migration2.png)
1078
1079           3. Click **Signing Configs** and click **OK**.
1080
1081           ![hop-cross-device-migration](figures/hop-cross-device-migration3.png)
1082
1083           4. Connect to the developer board and run the demo.
1084
1085**Device Networking**
1086
10871. Open the calculators of devices A and B.
10882. Click the arrow in the upper right corner to select device B.
10893. Select a trusted device on device B. The PIN is displayed.
10904. Enter the PIN on device A.
10915. Verify the networking. Enter a number on device A. If the number is displayed on device B, the networking is successful.
1092
1093**Initiation Migration**
1094
10951. Open your application on device B, and open the mission center demo on device A. The name of device A and the name of device B are displayed on the mission center demo.
10962. Touch the name of device B. The application card list of device B is displayed.
10973. Drag the application card to be connected to the name of device A. The application on device A is started.
1098
1099## FAQs
1100
1101### Q1: What Can I Do If the Application Cannot Be Migrated Back to the Source Device?
1102
1103The migration state is set at the ability level. The application on the target device may have executed the command to set its own migration state (for example, set the state to **INACTIVE** in [onCreate()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncreate) during cold start or to **INACTIVE** during hot start since the application has opened a page that cannot be migrated). To ensure a migration back to the source device, you must set the migration state to **ACTIVE** in onCreate() or [onNewWant()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonnewwant).
1104
1105```ts
1106// MigrationAbility.ets
1107import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
1108import { hilog } from '@kit.PerformanceAnalysisKit';
1109
1110const TAG: string = '[MigrationAbility]';
1111const DOMAIN_NUMBER: number = 0xFF00;
1112
1113export default class MigrationAbility extends UIAbility {
1114  // ...
1115  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
1116    // ...
1117    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
1118      // ...
1119      // Set the migration state to ACTIVE when the launch is caused by migration. This setting copes with cold start.
1120      this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
1121        hilog.info(DOMAIN_NUMBER, TAG, `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
1122      });
1123    }
1124    // ...
1125  }
1126
1127  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
1128    // ...
1129    // Set the migration state to ACTIVE when the launch is caused by migration. This setting copes with hot start.
1130    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
1131      this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
1132        hilog.info(DOMAIN_NUMBER, TAG, `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
1133      });
1134    }
1135  }
1136  // ...
1137}
1138```
1139
1140### Q2: What Can I Do If the Call of loadContent() Does Not Take Effect in onWindowStageRestore()?
1141
1142If page stack migration is not disabled for an application, the system migrates and loads the page stack of the application by default. In this case, if you use [loadContent()](../reference/apis-arkui/js-apis-window.md#loadcontent9) to trigger the loading of a specific page in the [onWindowStageRestore()](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityonwindowstagerestore) lifecycle callback function, the loading does not take effect and the page in the page stack is still restored.
1143
1144