跨端遷移
概述
在用戶使用設備的過程中,當使用情境發生變化時(例如從室內走到戶外或者周圍有更適合的設備等),之前使用的設備可能已經不適合繼續當前的任務,此時,用戶可以選擇新的設備來繼續當前的任務,原設備可按需決定是否退出任務,這個就是跨端遷移的場景。常見的跨端遷移場景實例:在平板上播放的視頻,遷移到智慧屏繼續播放,從而獲得更佳的觀看體驗;平板上的視頻應用退出。在應用開發層面,跨端遷移指在A端運行的UIAbility遷移到B端上,完成遷移后,B端UIAbility繼續任務,而A端UIAbility可按需決定是否退出。
跨端遷移的核心任務是將應用的當前狀態(包括頁面控件、狀態變量等)無縫遷移到另一設備,從而在新設備上無縫接續應用體驗。這意味著用戶在一臺設備上進行的操作可以在另一臺設備的相同應用中快速切換并無縫銜接。
主要功能包括:
- 支持用戶自定義數據存儲及恢復。
- 支持頁面路由信息和頁面控件狀態數據的存儲及恢復。
- 支持應用兼容性檢測。
- 支持應用根據實際使用場景動態設置遷移狀態(默認遷移狀態為 ACTIVE 激活狀態)。例如,編輯類應用在編輯文本的頁面下才需要遷移,其他頁面不需要遷移,則可以通過[
setMissionContinueState
]進行控制。 - 支持應用動態選擇是否進行頁面棧恢復(默認進行頁面棧信息恢復)。例如,應用希望自定義遷移到其他設備后顯示的頁面,則可以通過[
SUPPORT_CONTINUE_PAGE_STACK_KEY
]進行控制。 - 支持應用動態選擇遷移成功后是否退出遷移源端應用(默認遷移成功后退出遷移源端應用)。可以通過[
SUPPORT_CONTINUE_SOURCE_EXIT_KEY
]進行控制。說明:
開發者可以開發具有遷移能力的應用,遷移的觸發由系統應用完成。
運作機制
- 在源端,通過
UIAbility
的[onContinue()
]回調,開發者可以保存待接續的業務數據。例如,在瀏覽器應用中完成跨端遷移,開發者需要使用[onContinue()
]回調保存頁面URL等業務內容,而系統將自動保存頁面狀態,如當前瀏覽進度。 - 分布式框架提供了跨設備應用界面、頁面棧以及業務數據的保存和恢復機制,它負責將數據從源端發送到對端。
- 在對端,同一
UIAbility
可以通過[onCreate()
](冷啟動)和[onNewWant()
](熱啟動)接口來恢復業務數據。
跨端遷移流程
以從對端的遷移入口發起遷移為例,跨端遷移流程如下圖所示。
約束限制
- 跨端遷移要求在同一
UIAbility
之間進行,也就是需要相同的bundleName
、abilityName
和簽名信息。 - 為了獲得最佳體驗,使用
wantParam
傳輸的數據需要控制在100KB以下。
開發步驟
在[module.json5配置文件]的abilities標簽中配置跨端遷移標簽
continuable
。{ "module": { // ... "abilities": [ { // ... "continuable": true, // 配置UIAbility支持遷移 } ] } }
說明:
根據需要配置應用啟動模式類型,配置詳情請參照[UIAbility組件啟動模式]。
在源端
UIAbility
中實現[onContinue()
]回調。
當UIAbility
實例觸發遷移時,[onContinue()
]回調在源端被調用,開發者可以在該接口中通過同步或異步的方式來保存遷移數據,實現應用兼容性檢測,決定是否支持此次遷移。- 保存遷移數據:開發者可以將要遷移的數據通過鍵值對的方式保存在
wantParam
參數中。 - 應用兼容性檢測:開發者可以通過從
wantParam
參數中獲取對端應用的版本號與源端應用版本號做兼容性校驗。開發者可以在觸發遷移時從[onContinue()
]回調中wantParam.version
獲取到遷移對端應用的版本號與遷移源端應用版本號做兼容校驗。 - 遷移決策:開發者可以通過[
onContinue()
]回調的返回值決定是否支持此次遷移,接口返回值詳見[AbilityConstant.OnContinueResult
]。
import AbilityConstant from '@ohos.app.ability.AbilityConstant'; import hilog from '@ohos.hilog'; import UIAbility from '@ohos.app.ability.UIAbility'; const TAG: string = '[MigrationAbility]'; const DOMAIN_NUMBER: number = 0xFF00; export default class MigrationAbility extends UIAbility { onContinue(wantParam: Record< string, Object >):AbilityConstant.OnContinueResult { let version = wantParam.version; let targetDevice = wantParam.targetDevice; hilog.info(DOMAIN_NUMBER, TAG, `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; } }
- 保存遷移數據:開發者可以將要遷移的數據通過鍵值對的方式保存在
源端設備
UIAbility
實例在冷啟動和熱啟動情況下分別會調用不同的接口來恢復數據和加載UI。 在對端設備的UIAbility
中,需要實現[onCreate()
]/[onNewWant()
]接口來恢復遷移數據。不同情況下的函數調用如下圖所示:- 通過在[
onCreate()
]/[onNewWant()
]回調中檢查launchReason
,可以判斷此次啟動是否有遷移觸發。 - 開發者可以從
want
中獲取之前保存的遷移數據 - 數據恢復后調用
restoreWindowStage()
來觸發 頁面恢復 ,包括頁面棧信息。
import AbilityConstant from '@ohos.app.ability.AbilityConstant'; import hilog from '@ohos.hilog'; import UIAbility from '@ohos.app.ability.UIAbility'; import type Want from '@ohos.app.ability.Want'; const TAG: string = '[MigrationAbility]'; const DOMAIN_NUMBER: number = 0xFF00; export default class MigrationAbility 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.CONTINUATION) { // 將上述保存的數據從want.parameters中取出恢復 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); } } onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { hilog.info(DOMAIN_NUMBER, TAG, 'onNewWant'); if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) { // 將上述保存的數據從want.parameters中取出恢復 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); } } }
- 通過在[
可選配置遷移能力
動態配置遷移能力
從API version 10開始,提供了支持動態配置遷移能力的功能。即應用可以根據實際使用場景,在需要遷移時開啟應用遷移能力;在業務不需要遷移時則可以關閉遷移能力。
開發者可以通過調用[setMissionContinueState()
]接口對遷移能力進行設置。默認狀態下,應用的遷移能力為ACTIVE狀態,即遷移能力開啟,可以遷移。
設置遷移能力的時機
遷移能力的改變可以根據實際業務需求和代碼實現,發生在應用生命周期的絕大多數時機。本文介紹常用的幾種配置方式。
在UIAbility
的[onCreate()
]回調中調用接口,可以在應用創建時設置應用的遷移狀態。
// MigrationAbility.ets
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import hilog from '@ohos.hilog';
import UIAbility from '@ohos.app.ability.UIAbility';
import type Want from '@ohos.app.ability.Want';
const TAG: string = '[MigrationAbility]';
const DOMAIN_NUMBER: number = 0xFF00;
export default class MigrationAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// ...
this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) = > {
hilog.info(DOMAIN_NUMBER, TAG, `setMissionContinueState INACTIVE result: ${JSON.stringify(result)}`);
});
// ...
}
}
在頁面的onPageShow()
回調中調用接口,可以設置單個頁面出現時應用的遷移狀態。
// Page_MigrationAbilityFirst.ets
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';
const TAG: string = '[MigrationAbility]';
const DOMAIN_NUMBER: number = 0xFF00;
@Entry
@Component
struct Page_MigrationAbilityFirst {
private context = getContext(this) as common.UIAbilityContext;
build() {
// ...
}
// ...
onPageShow(){
// 進入該頁面時,將應用設置為可遷移狀態
this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) = > {
hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
});
}
}
在某個組件的觸發事件中設置應用遷移能力。
// Page_MigrationAbilityFirst.ets
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';
import promptAction from '@ohos.promptAction'
import router from '@ohos.router';
const TAG: string = '[MigrationAbility]';
const DOMAIN_NUMBER: number = 0xFF00;
@Entry
@Component
struct Page_MigrationAbilityFirst {
private context = getContext(this) as common.UIAbilityContext;
build() {
Column() {
//...
List({ initialIndex: 0 }) {
ListItem() {
Row() {
//...
}
.onClick(() = > {
// 點擊該按鈕時,將應用設置為可遷移狀態
this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) = > {
hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
promptAction.showToast({
message: $r('app.string.Success')
});
});
})
}
//...
}
//...
}
//...
}
}
保證遷移連續性
由于遷移加載時,對端拉起的應用可能執行過自己的遷移狀態設置命令(例如,冷啟動時對端在[onCreate()
]中設置了 INACTIVE ;熱啟動時對端已打開了不可遷移的頁面,遷移狀態為 INACTIVE 等情況)。為了保證遷移過后的應用依然具有可以遷移回源端的能力,應在 [onCreate()
]/[onNewWant()
]的遷移調用判斷中,將遷移狀態設置為 ACTIVE 。
// MigrationAbility.ets
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import type Want from '@ohos.app.ability.Want';
const TAG: string = '[MigrationAbility]';
const DOMAIN_NUMBER: number = 0xFF00;
export default class MigrationAbility extends UIAbility {
// ...
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// ...
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
// ...
}
// 調用原因為遷移時,設置狀態為可遷移,應對冷啟動情況
this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) = > {
hilog.info(DOMAIN_NUMBER, TAG, `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
});
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// ...
// 調用原因為遷移時,設置狀態為可遷移,應對熱啟動情況
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) = > {
hilog.info(DOMAIN_NUMBER, TAG, `setMissionContinueState ACTIVE result: ${JSON.stringify(result)}`);
});
}
}
// ...
}
按需遷移頁面棧
支持應用動態選擇是否進行頁面棧恢復(默認進行頁面棧信息恢復)。如果應用不想使用系統默認恢復的頁面棧,則可以設置不進行頁面棧遷移,而需要在onWindowStageRestore()
設置遷移后進入的頁面,參數定義見[SUPPORT_CONTINUE_PAGE_STACK_KEY]。
應用在源端的頁面棧中存在Index和Second路由,而在對端恢復時不需要按照源端頁面棧進行恢復,需要恢復到指定頁面。
例如,UIAbility
遷移不需要自動遷移頁面棧信息。
// MigrationAbility.ets
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import UIAbility from '@ohos.app.ability.UIAbility';
import wantConstant from '@ohos.app.ability.wantConstant';
import hilog from '@ohos.hilog';
import type window from '@ohos.window';
const TAG: string = '[MigrationAbility]';
const DOMAIN_NUMBER: number = 0xFF00;
export default class MigrationAbility extends UIAbility {
// ...
onContinue(wantParam: Record< string, Object >):AbilityConstant.OnContinueResult {
hilog.info(DOMAIN_NUMBER, TAG, `onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`);
wantParam[wantConstant.Params.SUPPORT_CONTINUE_PAGE_STACK_KEY] = false;
return AbilityConstant.OnContinueResult.AGREE;
}
onWindowStageRestore(windowStage: window.WindowStage) : void {
// 若不需要自動遷移頁面棧信息,則需要在此處設置應用遷移后進入的頁面
windowStage.loadContent('pages/page_migrationability/Page_MigrationAbilityThird', (err, data) = > {
if (err.code) {
hilog.error(DOMAIN_NUMBER, TAG, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
});
}
}
按需退出
支持應用動態選擇遷移成功后是否退出遷移源端應用(默認遷移成功后退出遷移源端應用)。如果應用不想讓系統自動退出遷移源端應用,則可以設置不退出,參數定義見[SUPPORT_CONTINUE_SOURCE_EXIT_KEY]。
示例:UIAbility
設置遷移成功后,源端不需要退出遷移應用。
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import UIAbility from '@ohos.app.ability.UIAbility';
import wantConstant from '@ohos.app.ability.wantConstant';
import hilog from '@ohos.hilog';
const TAG: string = '[MigrationAbility]';
const DOMAIN_NUMBER: number = 0xFF00;
export default class MigrationAbility extends UIAbility {
// ...
onContinue(wantParam: Record< string, Object >):AbilityConstant.OnContinueResult {
hilog.info(DOMAIN_NUMBER, TAG, `onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`);
wantParam[wantConstant.Params.SUPPORT_CONTINUE_SOURCE_EXIT_KEY] = false;
return AbilityConstant.OnContinueResult.AGREE;
}
}
跨端遷移中的數據遷移
當前推薦兩種不同的數據遷移方式,開發者可以根據實際使用需要進行選擇。
說明:
部分ArkUI組件支持通過配置
restoreId
的方式,在遷移后將特定狀態恢復到對端設備。
如果涉及分布式對象遷移時應注意:
- 需要申請
ohos.permission.DISTRIBUTED_DATASYNC
權限。- 同時需要在應用首次啟動時彈窗向用戶申請授權。
使用wantParam遷移數據
在需要遷移的數據較少(100KB以下)時,開發者可以選擇在wantParam
中增加字段進行數據遷移。示例如下:
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import UIAbility from '@ohos.app.ability.UIAbility';
import type Want from '@ohos.app.ability.Want';
const TAG: string = '[MigrationAbility]';
const DOMAIN_NUMBER: number = 0xFF00;
export default class MigrationAbility extends UIAbility {
// 源端保存
onContinue(wantParam: Record< string, Object >):AbilityConstant.OnContinueResult {
// 將要遷移的數據保存在wantParam的自定義字段(例如data)中
const continueInput = '遷移的數據';
wantParam['data'] = continueInput;
return AbilityConstant.OnContinueResult.AGREE;
}
// 對端恢復
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
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 ${continueInput}`);
}
// 觸發頁面恢復
this.context.restoreWindowStage(this.storage);
}
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
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);
}
}
}
使用分布式對象遷移數據
當需要遷移的數據較大(100KB以上)或數據以文件形式存在時,可以選擇[分布式對象]進行數據遷移。其中,對于文件的遷移,可以使用分布式對象提供的[資產]遷移方式進行。
- 在源端
onContinue()
接口中創建一個分布式數據對象[DataObject
],將所要遷移的數據填充到分布式對象數據中,并將生成的sessionId
通過want
傳遞到對端。 - 對端在
onCreate()/onNewWant
中進行數據恢復時,可以從want中讀取該sessionId
,通過分布式對象恢復數據。
說明: 自API 12起,為了保證更高的成功率,文件數據的遷移不建議繼續通過[跨設備文件訪問]實現,推薦使用分布式對象攜帶資產的方式進行。開發者此前通過跨設備文件訪問實現的文件遷移依然可以使用。
驗證指導
為方便開發者驗證已開發的可遷移應用,系統提供了一個全局任務中心demo作為遷移的入口。下面介紹通過安裝全局任務中心來驗證遷移的方式。
1. 編譯安裝全局任務中心
配置環境
public-SDK不支持開發者使用所有的系統API,例如:全局任務中心使用的[ @ohos.distributedDeviceManager ]不包括在public_SDK中。因此為了正確編譯安裝全局任務中心,開發者需要替換full-SDK,具體操作可參見[替換指南]。
說明 :
本文中的截圖僅為參考,具體的顯示界面請以實際使用的DevEco Studio和SDK的版本為準。
下載MissionCenter_Demo[示例代碼]
編譯工程文件
?a.新建一個工程,找到對應的文件夾替換下載文件
?b.自動簽名,編譯安裝。
?DevEco的自動簽名模板默認簽名權限為normal級。而本應用設計到ohos.permission.MANAGE_MISSIONS權限為system_core級別。自動生成的簽名無法獲得足夠的權限,所以需要將權限升級為system_core級別,然后簽名。
?c.系統權限設置(以api10目錄為例): 將Sdk目錄下的openharmonyapi版本(如:10)toolchainslibUnsignedReleasedProfileTemplate.json文件中的"apl":"normal"改為"apl":"system_core"。
- 點擊file->Project Structure。
- 點擊Signing Configs 點擊OK。
- 連接開發板運行生成demo。
2. 設備組網
- 打開A,B兩設備的計算器。
- 點擊右上角箭頭選擇B設備。
- 在B設備選擇信任設備,彈出PIN碼。
- 在A設備輸入PIN碼。
- 已組網成功,驗證方法:在A設備輸入數字,B設備同步出現則證明組網成功。
3. 發起遷移
在B設備打開多設備協同權限的應用,A設備打開全局任務中心demo,A設備出現A設備名稱(即:本機:OpenHarmony 3.2)和B設備名稱(OpenHarmony 3.2)。
點擊B設備名稱,然后出現B設備的應用。
最后將應用拖拽到A設備名稱處,A設備應用被拉起,B設備應用退出。
.審核編輯 黃宇
-
組件
+關注
關注
1文章
512瀏覽量
17813 -
SDK
+關注
關注
3文章
1035瀏覽量
45899 -
鴻蒙
+關注
關注
57文章
2339瀏覽量
42805
發布評論請先 登錄
相關推薦
評論