跨设备迁移是指将应用中的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也需要实现此接口。