The app continuation feature allows the operations being performed by a user on one device to be quickly switched and seamlessly continued in the same app on another device. For example, when the environment changes during usage and a more appropriate device is detected, the user can migrate an ongoing task to another device without exiting. After the continuation is complete, the app on the previous device can be exited or retained. The target device continues the task.
As shown in the figure, notes being edited on the go can be seamlessly switched from the phone to the PC when the user arrives at the office.

The HarmonyOS underlying architecture provides solutions for device discovery, connection, and networking during app continuation. During app access, you only need to focus on data transfer and recovery. For details, see the following sections:
Devices running HarmonyOS NEXT Developer Preview0 or later.
App continuation is generally used for pages that users stay on for a long time. By default, after the continuation is complete, the app on the source device automatically exits. You can refer to Exit on Demand.
Recommended configuration items for different types of apps are as follows.
Category | Scenario | Page for Initiating Continuation and Content to Be Synchronized | Exit Solution for the App on the Source Device |
|---|---|---|---|
Tools | Notepad | On the details page of Notepad, the browsing progress is synchronized. | Exit. |
Notes | On the notes editing page, the browsing or editing progress is synchronized. | Exit. | |
Calendar | On any page, the date view, schedule browsing, or editing progress is synchronized. | Exit. | |
Email address | On the email editing page, the editing progress and attachments are synchronized. | Exit. | |
Browser | On the web page details page, the browsing progress is synchronized. | Exit. | |
Navigation & transport | Map service | On the route query or navigation page, the current route and navigation are synchronized. | Exit. |
Entertainment | Video/Short video/Micro-drama/Live streaming | On the audio/video playback or live streaming page, the audio/video playback progress or live streaming progress is synchronized. | App adaptation as required. |
Music/Karaoke/Radio/Musical instrument | On the audio/video playback or live streaming page, the audio/video playback progress or live streaming progress is synchronized. | App adaptation as required. | |
Kids | Early education/Nursery rhymes | On the audio/video playback or live streaming page, the audio/video playback progress or live streaming progress is synchronized. | App adaptation as required. |
News & reading | Audiobook/Podcast/Ebook/Novel/Magazine | On the book home page, novel reading page, or audiobook playback page, the reading progress or audiobook playback progress are synchronized. | Exit. However, for scenarios involving extensive audiobook listening or live streaming, the app may not exit but only pause the playback of the audiobook, or does not exit with live streaming continued. |
News | On the news details page, the news browsing progress is synchronized. | Exit. | |
Office
| Meeting | On a meeting page, the current meeting is synchronized. | Exit the meeting and navigate to the chat page without closing the app. |
Design | On the CAD editing screen, the editing content is synchronized. | App adaptation as required. | |
Social | Social media | On the image and text browsing, video browsing, and editing pages, the browsing and editing progress is synchronized. | App adaptation as required. |
Photo & video | Shooting/Photo beautification | On the photo or video editing page, the editing content and progress are synchronized. | Exit. |
Lifestyle
| Housing/Home decoration | App adaptation as required. | Exit. |
Recipe/Baking/Diet/Cookbook | App adaptation as required. | Exit. | |
Product recommendation | App adaptation as required. | Exit. | |
Finance | Stock/Stock market/Fund/Securities/Securities investment consulting | App adaptation as required. | Exit. |
Game | Game | App adaptation as required. | Exit. |

For example, to implement app continuation in a browser, you need to use the onContinue() API to save the service content, such as the page URL, so that the user can continue browsing the page on the target device after opening it on the source device.
The following table lists the main APIs for implementing app continuation. For details, see UIAbility.
API | Description |
|---|---|
onContinue(wantParam : {[key: string]: Object}): OnContinueResult | Called by the source app to save the data required for migration and specify whether the migration is allowed.
|
onCreate(want: Want, param: AbilityConstant.LaunchParam): void; | Called by the target to restore the data and UI page if the target uses cold start or the target is a multiton app and uses hot start. |
onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void; | Called by the target to restore the data and UI page if the target is a singleton app and uses hot start. |
In the abilities field of the module.json5 file, set continuable to true, which indicates that the UIAbility can be migrated. The default value is false, indicating that the migration cannot be performed.
{
"module": {
// ...
"abilities": [
{
// ...
"continuable": true,
// ...
}
],
// ...
}
} Configure the app launch type. For details, see UIAbility Component Launch Type.
When app data migration is triggered, onContinue() is called on the source device. You can implement the following in this callback:
If the compatibility issues have little or no impact on migration experience, you can skip this check.
Field | Description |
|---|---|
version | Version number of the app on the target device. |
targetDevice | Network ID of the target device. |
export default class EntryAbility extends UIAbility {
// Indicates whether target side should restore Router page stack automatically.
// ...
onContinue(wantParam: Record<string, Object>) {
// Read target app version to guard cross-version continuation compatibility.
const targetVersion = wantParam.version;
// Define minimum compatible version supported by the source app.
const versionThreshold: number = 1000;
// Reject continuation when target side is below required version.
if (targetVersion < versionThreshold) {
// Give user-friendly reason when continuation is rejected.q
this.context.windowStage.getMainWindowSync().getUIContext().getPromptAction().showToast({
message: 'The target app version is too early to continue. Update the app and try again.',
duration: 2000
})
// Return MISMATCH to terminate continuation negotiation.
return AbilityConstant.OnContinueResult.MISMATCH;
}
// Put migration payload into want params for target-side restore.
const continueInput = AppStorage.get('entryAbilityContext') as string;
if (continueInput) {
// Custom field "data" carries text payload.
wantParam['data'] = continueInput;
}
// ...
return AbilityConstant.OnContinueResult.AGREE;
}
// ...
} In the stage model, apps with different launch types call different APIs to restore data and load the UI, as shown below.

Implement the onCreate() and onNewWant() APIs in the UIAbility of the target device to restore the migrated data.
export default class EntryAbility extends UIAbility {
// Indicates whether target side should restore Router page stack automatically.
// ...
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
try {
// Keep system color mode unmanaged by the app.
// ...
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
// Restore migration payload and stack snapshot from continuation parameters.
this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
console.info(`setMissionContinueState: ${JSON.stringify(result)}`);
});
if (want.parameters !== undefined) {
if (typeof want.parameters.data === 'string') {
AppStorage.setOrCreate('entryAbilityContext', want.parameters.data);
}
// ...
}
// Restore window stage so UI can reconstruct previous continuation state.
this.context.restoreWindowStage(new LocalStorage());
}
} catch (err) {
hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
}
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
}
// ...
}
The restoreWindowStage(this.storage) API must be executed in a synchronous API method. If it is executed in an asynchronous callback, the page may fail to be loaded after app migration.
Generally, 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 tag under abilities in the module.json5 file for association. The values of the continueType tag of the two abilities must be the same. The following is an example.
Device A:
{
"module": {
// ...
"abilities": [
{
// ...
"continuable": true,
"continueType": ['continueType1'],
}
],
// ...
}
} Device B:
{
"module": {
// ...
"abilities": [
{
// ...
"continuable": true,
"continueType": ['continueType1']
}
],
// ...
}
} An app developed by the same developer may use different bundle names on different device types. To implement cross-device migration for the app, you need to perform the following configurations in the module.json5 configuration file of each bundle of the app:
The sample code is as follows:
An app with different bundle names is migrated between device A and device B. The bundle name of the app on device A is com.demo.example1, and that on device B is com.demo.example2.
{
"module": {
// ...
"abilities": [
{
"name": "EntryAbility"
// ...
"continueType": ['continueType'],
"continueBundleName": ["com.demo.example2"],
}
]
}
} In the app configuration file of device B, set the continueBundleName field to the bundle name of the app on device A. In this example, the bundle name of the app on device A is com.demo.example1.
{
"module": {
// ...
"abilities": [
{
"name": "EntryAbility"
// ...
"continueType": ['continueType'],
"continueBundleName": ["com.demo.example1"],
}
]
}
} An app may be released by different developers on different types of devices. In this scenario, if the app needs to support cross-device migration, you need to apply for the continuation service in AppGallery Connect.
The following example describes the operation procedure for an app released by two developers. The smartphone app A and PC app B belong to different developers. The app ID of app A is AppId_A, and that of app B is AppId_B.
UI display in the procedure may vary in different versions. The actual website prevails.

Multiple app IDs can be configured for app continuation. The configuration sequence determines the sequence in which apps are matched on the target device. When entering the app ID of the corresponding device type, ensure that your own app ID is included.
The recommended app ID configuration sequence is as follows.
Initiator | Target | App ID Configuration Priority of Initiator App A (from High to Low) |
|---|---|---|
Mobile device | TV/PC | 1. App A on the TV 2. App A on the PC 3. App A on the mobile device |
TV | PC/Mobile | 1. App A on the PC 2. App A on the TV 3. App A on the mobile device |
PC | TV/Mobile | 1. App A on the TV 2. App A on the PC 3. App A on the mobile device |




{
"module": {
// ...
"abilities": [
{
"name": "EntryAbility"
// ...
"continueType": ['continueType'],
"continueBundleName": ["com.demo.example2"],
}
]
}
} In the app configuration file of device B, set the continueBundleName field to the bundle name of the app on device A. In this example, the bundle name of the app on device A is com.demo.example1.
// In the configuration file for device B, set continueBundleName to the bundle name of the app on device A.
{
"module": {
// ...
"abilities": [
{
"name": "EntryAbility"
// ...
"continueType": ['continueType'],
"continueBundleName": ["com.demo.example1"],
}
]
}
} {
"module": {
// ...
"abilities": [
{
// ...
"name": "EntryAbility"
"continueType": ['EntryAbility_ContinueQuickStart'],
}
]
}
} If the quick start is configured for an app, the app will receive an onCreate() or onNewWant() request with launchReason.PREPARE_CONTINUATION when the user initiates the continuation, and then receive an onNewWant() request with launchReason.CONTINUATION, as described in the following table.
Scenario | Lifecycle Function | launchParam.launchReason |
|---|---|---|
First start request | onCreate (in the case of cold start); onNewWant (in the case of hot start) | AbilityConstant.LaunchReason.PREPARE_CONTINUATION |
Second start request | onNewWant | AbilityConstant.LaunchReason.CONTINUATION |

If quick start is not configured, only one start request is received.
Scenario | Lifecycle Function | launchParam.launchReason |
|---|---|---|
One start request | onCreate (in the case of cold start); onNewWant (in the case of hot start) | AbilityConstant.LaunchReason.CONTINUATION |
After quick start is configured, onCreate() or onNewWant() can be implemented as follows:
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
// ...
const TAG: string = '[MigrationAbility]';
const DOMAIN_NUMBER: number = 0xFF00;
export default class MigrationAbility_quickStart extends UIAbility {
storage : LocalStorage = new LocalStorage();
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Ability onCreate');
if (launchParam.launchReason === AbilityConstant.LaunchReason.PREPARE_CONTINUATION) {
// ...
}
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(DOMAIN_NUMBER, TAG, 'onNewWant');
if (launchParam.launchReason === AbilityConstant.LaunchReason.PREPARE_CONTINUATION) {
// ...
}
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
let continueInput = '';
if (want.parameters !== undefined) {
continueInput = JSON.stringify(want.parameters.data);
hilog.info(DOMAIN_NUMBER, TAG, `continue input ${JSON.stringify(continueInput)}`);
}
this.context.restoreWindowStage(this.storage);
}
}
// ...
}
Since API version 10, you can dynamically set the migration capability. Specifically, the app can enable or disable migration based on the service scenario by calling setMissionContinueState().
Value | Description |
|---|---|
AbilityConstant.ContinueState.ACTIVE | The migration capability is enabled for the app. |
AbilityConstant.ContinueState.INACTIVE | The migration capability is disabled for the app. |
Time for Setting the Migration Capability
By default, AbilityConstant.ContinueState.ACTIVE is set for an app, which indicates that migration is enabled.
To implement special scenarios, for example, where migration is required only for a specific page or upon a specific event, perform the following steps:
private setContinueEnabled(isOn: boolean): void {
const state = isOn ? AbilityConstant.ContinueState.ACTIVE : AbilityConstant.ContinueState.INACTIVE;
this.context.setMissionContinueState(state, (result) => {
hilog.info(0x0000,`setMissionContinueState ${state} result: `, JSON.stringify(result));
});
}
onPageShow() {
// Ensure continuation is active when this page is visible.
this.setContinueEnabled(true);
} Toggle({ type: ToggleType.Switch, isOn: true })
.selectedColor($r('sys.color.icon_emphasize'))
.switchPointColor(Color.White)
.onChange((isOn: boolean) => {
// Sync ability continuation state with UI switch.
this.setContinueEnabled(isOn);
}) Ensuring Migration Continuity
During UI page loading, the app on the target device may have executed the command to set its own migration state (for example, set the state to INACTIVE in onCreate() during cold start or to INACTIVE during hot start after the app has opened a page that cannot be migrated). To ensure the app can be migrated back to the source device, you must set the migration state to ACTIVE in onCreate() and onNewWant().
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
try {
// ...
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
// Restore migration payload and stack snapshot from continuation parameters.
this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
console.info(`setMissionContinueState: ${JSON.stringify(result)}`);
});
// ...
// Restore window stage so UI can reconstruct previous continuation state.
this.context.restoreWindowStage(new LocalStorage());
}
} catch (err) {
hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
}
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
try {
// Keep system color mode unmanaged by the app.
// ...
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
// Restore migration payload and stack snapshot from continuation parameters.
this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
console.info(`setMissionContinueState: ${JSON.stringify(result)}`);
});
// ...
// Restore window stage so UI can reconstruct previous continuation state.
this.context.restoreWindowStage(new LocalStorage());
}
} catch (err) {
hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
}
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
// ...
} Apps can dynamically determine whether to restore the page stack (restored by default). If the app on the target device does not use the default page stack restored, disable page stack migration and set the page to be displayed in onWindowStageRestore(). For details about the parameter definition, see SUPPORT_CONTINUE_PAGE_STACK_KEY in Params.
The index and second routes exist in the page stack of the app on the source device. However, the app on the target device wants to display a specified page, rather than that on the source device.
onContinue(wantParam: Record<string, Object>) {
// ...
wantParam[wantConstant.Params.SUPPORT_CONTINUE_PAGE_STACK_KEY] = AppStorage.get('ifContinueRouterPage') as boolean;
// Return AGREE to allow continuation process to proceed.
return AbilityConstant.OnContinueResult.AGREE;
} export default class EntryAbility extends UIAbility {
// Indicates whether target side should restore Router page stack automatically.
SUPPORT_CONTINUE_PAGE_STACK_KEY:boolean = true;
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
try {
// ...
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
// Restore migration payload and stack snapshot from continuation parameters.
if (want.parameters !== undefined) {
// ...
if (typeof want.parameters['ohos.extra.param.key.supportContinuePageStack'] === 'boolean') {
this.SUPPORT_CONTINUE_PAGE_STACK_KEY =
want.parameters['ohos.extra.param.key.supportContinuePageStack'] as boolean;
}
}
// Restore window stage so UI can reconstruct previous continuation state.
this.context.restoreWindowStage(new LocalStorage());
}
} catch (err) {
hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
}
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
}
// ...
} onWindowStageRestore(windowStage: window.WindowStage) {
// If Router stack migration is disabled, force fallback landing page.
if(!this.SUPPORT_CONTINUE_PAGE_STACK_KEY){
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
}
} You can determine whether to exit the app on the source device after a successful migration (the app exits by default). To prevent the app on the source device from automatically exiting after a successful migration, set the SUPPORT_CONTINUE_SOURCE_EXIT_KEY parameter in Params.
Example: An app on the source device does not exit after a successful migration.
onContinue(wantParam: Record<string, Object>) {
// ...
// Export Router/source-exit switches for target-side behavior control.
wantParam[wantConstant.Params.SUPPORT_CONTINUE_SOURCE_EXIT_KEY] = AppStorage.get('entryAbilityContinueExit') as boolean;
// ...
return AbilityConstant.OnContinueResult.AGREE;
} Symptom
After a system app such as the system browser is opened, the continuation icon is not displayed on the target device.
Solution
If no continuation icon is displayed on the target device after the system browser, a memo note with content, or a newly developed app is opened, perform the following steps to troubleshoot:
hidumper -s 4700 -a "buscenter -l remote_device_info"
After the command is executed, the devices listed in RemoteDeviceInfo are the devices that have been successfully networked with the current device. As shown in the following figure, the device has been successfully networked with two other devices.

Symptom
When no operation is performed on the app within 1 minute, the icon automatically disappears, and reappears when the app is operated.
Possible Causes
Icon display and hiding is a feature of the system. According to the current policy, the icon is displayed within 1 minute after the last touch operation on the source device. If no operation is performed within 1 minute, the icon automatically disappears to reduce interference to users. Similarly, when the screen is locked, the icon automatically disappears after 10 seconds, which is also a part of the normal system running.
Symptom
The continuation icon is properly displayed for system apps such as the system browser, but not for newly accessed apps.
Solution
A continuation broadcast is sent and the continuation icon is displayed on the target device only when the app is configured with the continuable tag and is in the focus and continuation state. Perform the following steps to troubleshoot: