PageAbility跨設備遷移開發實戰—問答互動
跨設備遷移是指將應用中的Page頁遷移到另一設備中。可以同步應用數據,甚至可以在的不同設備間遷移,是HarmonyOS特色之一。于是,我以官方給了分布式郵件系統為例,寫了一個簡單的問答互動應用。用戶在設備A上提問,在設備B上回答,信息通過遷移傳遞,并且能查看問答記錄。
Table of Contents
效果展示

主要功能
實現問答界面,通過發送按鈕將問題、答題等信息轉遞到另一設備上。
實現問題記錄界面,對每個完整的問答進行記錄,方便查看。
設備間的數據進行同步,擁有相同的問答記錄。
遷移的主要步驟
- 設備A上的Page請求遷移。
- HarmonyOS處理遷移任務,并回調設備A上Page的保存數據方法,用于保存遷移必須的數據。
- HarmonyOS在設備B上啟動同一個Page,并回調其恢復數據方法。
PageAbility實現遷移是需要實現IAbilityContinuation接口的,該接口如下:
- //
- // Source code recreated from a .class file by IntelliJ IDEA
- // (powered by FernFlower decompiler)
- //
- package ohos.aafwk.ability;
- import ohos.aafwk.content.IntentParams;
- public interface IAbilityContinuation {
- int ERR_ABILITY_QUERY_FAILED = -2;
- int ERR_CONTINUE_TIMEOUT = -8;
- int ERR_DEVICE_OFFLINE = -9;
- int ERR_INSTALL_FREE_NOT_SUPPORTED = -4;
- int ERR_NETWORK_UNAVAILABLE = -3;
- int ERR_PARAMETER_INVALID = -6;
- int ERR_PERMISSION_DENIED = -5;
- int ERR_REMOTE_DEVICE_INCOMPATIBLE = -7;
- int ERR_UNKNOWN = -1;
- int SUCCESS = 0;
- boolean onStartContinuation();
- boolean onSaveData(IntentParams var1);
- boolean onRestoreData(IntentParams var1);
- void onCompleteContinuation(int var1);
- default void onRemoteTerminated() {
- throw new RuntimeException("Stub!");
- }
- default void onFailedContinuation(int errorCode) {
- throw new RuntimeException("Stub!");
- }
- }
除了一些異常碼枚舉外,都是遷移中需要用到的主要接口,onStartContinuation()是遷移開始前的預處理函數,可以在這加一些條件檢測,提示等。但是在開始請求遷移前,需要申請權限ohos.permission.DISTRIBUTED_DATASYNC。config.json中的配置如下:
config.json
- "reqPermissions": [
- {
- "name": "ohos.permission.DISTRIBUTED_DATASYNC"
- }
- ]
接下來只需要PageAbility實現Ability中的onRequestPermissionsFromUserResult接口,就能在啟用遷移之前完成權限申請了。
- @Override
- public void onRequestPermissionsFromUserResult(int requestCode, String[] permissions, int[] grantResults) {
- if (permissions == null || permissions.length == 0 || grantResults == null || grantResults.length == 0) {
- return;
- }
- if (requestCode == 0) {
- if (grantResults[0] == IBundleManager.PERMISSION_DENIED) {
- terminateAbility();
- }
- }
- }
完成權限申請后,只需要通過事件來觸發遷移開關就行了。可以通過按鈕的點擊事件的來觸發遷移開關continueAbility(),如下:
- private void initComponents() {
- questionTextField = (TextField) findComponentById(ResourceTable.Id_question_content);
- answerTextField = (TextField) findComponentById(ResourceTable.Id_answer_content);
- findComponentById(ResourceTable.Id_send_button).setClickedListener(this::migrateAbility);
- findComponentById(ResourceTable.Id_return_button).setClickedListener(component->terminate());
- }
- private void migrateAbility(Component component) {
- String questionSend = questionTextField.getText();
- String answerSend = answerTextField.getText();
- if (questionSend.isEmpty() && answerSend.isEmpty()) {
- new ToastDialog(this).setText("Text can not be null").show();
- return;
- }
- try {
- continueAbility();
- } catch (IllegalStateException illegalStateException) {
- HiLog.error(LABEL_LOG, "%{public}s", "migrateAbility: IllegalStateException");
- }
- }
最重要的兩個接口莫過于onSaveData、onRestoreData了,一個是在遷移的時候,將設備A的需要輸入的數據存儲,另一個是在設備B進行遷移時,恢復數據。
- @Override
- public boolean onSaveData(IntentParams intentParams) {
- intentParams.setParam(QUESTION_KEY, questionTextField.getText());
- intentParams.setParam(ANSWER_KEY, answerTextField.getText());
- return true;
- }
- @Override
- public boolean onRestoreData(IntentParams intentParams) {
- if (intentParams.getParam(QUESTION_KEY) instanceof String) {
- questionText = (String) intentParams.getParam(QUESTION_KEY);
- }
- if (intentParams.getParam(ANSWER_KEY) instanceof String) {
- answerText = (String) intentParams.getParam(ANSWER_KEY);
- }
- if (!questionText.isEmpty() && ! answerText.isEmpty()) {
- AskRecordSlice.UpdateContent("Q:" + questionText + "\n");
- AskRecordSlice.UpdateContent("A:" + answerText + "\n");
- }
- return true;
- }
其中的IntentParams是遷移的數據包,提供了setParam、getParam,來傳輸Key-Value數據。
設備B上只要正常運行了onRestoreData后,那就會回調設備A上的onCompleteContinuation,表示遷移順利完成,否則回調onFailedContinuation,通過捕捉異常碼可進行異常處理。而我在正常遷移完成后,進行了問答記錄的本地存儲:
- @Override
- public void onCompleteContinuation(int code) {
- questionText = questionTextField.getText();
- answerText = answerTextField.getText();
- if (!questionText.isEmpty() && ! answerText.isEmpty()) {
- AskRecordSlice.UpdateContent("Q:" + questionText + "\n");
- AskRecordSlice.UpdateContent("A:" + answerText + "\n");
- }
- }
具體代碼
由于目錄樹中文件較多,整個工程文件的git路徑為:
https://gitee.com/baboon-chen/harmony-osexample.git
需要特殊注意的點:
- //1 跨不同設備時,需要在配置文件中添加上支持的設備類型 config.json
- "deviceType": [
- "phone",
- "tablet"
- ],
- //2 要實現接口的類有哪些?
- 一個應用可能包含多個Page,都有自己的PageSlice棧。僅需要在支持遷移的Page中通過以下方法實現IAbilityContinuation接口。同時,此Page所包含的所有AbilitySlice也需要實現此接口。