
01
分布式任务调度概述
在 HarmonyOS 中,分布式任务调度平台对搭载 HarmonyOS 的多设备构筑的“超级虚拟终端”提供统一的组件管理能力,为应用定义统一的能力基线、接口形式、数据结构、服务描述语言,屏蔽硬件差异;支持远程启动、远程调用、业务无缝迁移等分布式任务。
02
实现调度的约束与限制
①远程调用 PA/FA,开发者需要在 Intent 中设置支持分布式的标记(例如:Intent.FLAG_ABILITYSLICE_MULTI_DEVICE 表示该应用支持分布式调度),否则将无法获得分布式能力。
②开发者通过在 config.json 中的 reqPermissions 字段里添加权限申请,以获取跨设备连接的能力和分布式数据传输的权限:
{"name": "ohos.permission.servicebus.ACCESS_SERVICE"}
{"name": "ohos.permission.servicebus.DISTRIBUTED_DATASYNC"}
{"name": "com.huawei.hwddmp.servicebus.BIND_SERVICE"}
{"name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE"}, {"name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO" }, { "name": "ohos.permission.GET_BUNDLE_INFO"}
注意:还需要在开发的时候,要在 Ability 里主动声明,要用到的权限。
FA(Feature Ability,Page 模板的 Ability)的调用支持启动和迁移行为,在进行调度时:
当启动 FA 时,需要开发者在 Intent 中指定对端设备的 deviceId、bundleName 和 abilityName。
FA 的迁移实现相同 bundleName 和 abilityName 的 FA 跨设备迁移,因此需要指定迁移设备的 deviceId。
03
实现场景介绍
下面以设备 A(本地设备)和设备 B(远端设备)为例,介绍下面我们要实现的场景:
设备 A 启动设备 B 的 FA:在设备 A 上通过本地应用提供的启动按钮,启动设备 B 上对应的 FA。
设备 A 的 FA 迁移至设备 B:设备 A 上通过本地应用提供的迁移按钮,将设备 A 的业务无缝迁移到设备 B 中。
设备 A 的 FA 迁移至设备 B,还可以实现主动撤回迁移。
04
具体实现前先了解要用的接口
①启动远程 FA
startAbility(Intent intent)接口提供启动指定设备上 FA 和 PA 的能力,Intent 中指定待启动 FA 的设备 deviceId、bundleName 和 abilityName。
②迁移 FA
continueAbility(String deviceId)接口提供将本地 FA 迁移到指定设备上的能力。
continueAbilityReversibly(String deviceId)接口提供将本地 FA 迁移到指定设备上的能力,这种迁移可撤回,reverseContinueAbility() 接口提供撤回迁移的能力。
05
实战远程启动 FA 页面
①编程实现上面场景的界面
<?xml version="1.0" encoding="utf-8"?><DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_parent" ohos:width="match_parent" ohos:orientation="vertical"> <Button ohos:id="$+id:migration_btn_01" ohos:height="match_content" ohos:width="300vp" ohos:text="1.启动远程设备的FA" ohos:text_size="20fp" ohos:text_color="#ffffff" ohos:background_element="$graphic:button_bg" ohos:layout_alignment="horizontal_center" ohos:top_padding="8vp" ohos:bottom_padding="8vp" ohos:left_padding="40vp" ohos:right_padding="40vp" ohos:top_margin="20vp" /> <Button ohos:id="$+id:migration_btn_02" ohos:height="match_content" ohos:width="300vp" ohos:text="2.迁移到远程设备" ohos:text_size="20fp" ohos:text_color="#ffffff" ohos:background_element="$graphic:button_bg" ohos:layout_alignment="horizontal_center" ohos:top_padding="8vp" ohos:bottom_padding="8vp" ohos:left_padding="40vp" ohos:right_padding="40vp" ohos:top_margin="20vp" /> <Button ohos:id="$+id:migration_btn_03" ohos:height="match_content" ohos:width="300vp" ohos:text="3.可迁回的迁移远程设备" ohos:text_size="20fp" ohos:text_color="#ffffff" ohos:background_element="$graphic:button_bg" ohos:layout_alignment="horizontal_center" ohos:top_padding="8vp" ohos:bottom_padding="8vp" ohos:left_padding="40vp" ohos:right_padding="40vp" ohos:top_margin="20vp" /></DirectionalLayout>
<?xml version="1.0" encoding="utf-8"?><shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="rectangle"> <solid ohos:color="#007DFF"/> <corners ohos:radius="40"/></shape>
// 调用AbilitySlice模板实现一个用于控制基础功能的FA// Ability和AbilitySlice类均需要实现IAbilityContinuation及其方法,才可以实现FA迁移。AbilitySlice的代码示例如下public class SampleSlice extends AbilitySlice implements IAbilityContinuation { @Override public void onStart(Intent intent) { super.onStart(intent); super.setUIContent(layout); }
<?xml version="1.0" encoding="utf-8"?><DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_parent" ohos:width="match_parent" ohos:background_element="#00ffff" ohos:orientation="vertical"> <Text ohos:id="$+id:text_title" ohos:height="match_content" ohos:width="250vp" ohos:background_element="#0088bb" ohos:layout_alignment="horizontal_center" ohos:text="下面是一个可编辑的文本框" ohos:text_size="50" ohos:padding="5vp" ohos:top_margin="30vp" /> <TextField ohos:id="$+id:textfield_back" ohos:height="250vp" ohos:width="250vp" ohos:hint="请输入..." ohos:layout_alignment="horizontal_center" ohos:background_element="#ffffff" ohos:text_color="#888888" ohos:text_size="20fp" ohos:padding="5vp" /> <Button ohos:id="$+id:migration_button" ohos:height="match_content" ohos:width="match_content" ohos:text="点击迁移" ohos:text_size="20fp" ohos:text_color="#ffffff" ohos:background_element="$graphic:button_bg" ohos:top_padding="8vp" ohos:bottom_padding="8vp" ohos:left_padding="50vp" ohos:right_padding="50vp" ohos:layout_alignment="horizontal_center" ohos:top_margin="30vp" /></DirectionalLayout>
ability_migration_back.xml 比 ability_migration.xml 多一个迁回按钮,另外主页上点击按钮跳转等,略...
②使用分布式能力要求开发者在 Ability 对应的 config.json 中声明多设备协同访问的权限:
{ "reqPermissions": [ {"name": "ohos.permission.DISTRIBUTED_DATASYNC"}, {"name": "ohos.permission.servicebus.ACCESS_SERVICE"}, {"name": "com.huawei.hwddmp.servicebus.BIND_SERVICE"} ]}
{ "reqPermissions": [ {"name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE"}, {"name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO" }, {"name": "ohos.permission.GET_BUNDLE_INFO"} ]}
public class SampleSlice extends AbilitySlice implements IAbilityContinuation { @Override public void onStart(Intent intent) { // 开发者显示声明需要使用的权限 requestPermissionsFromUser(new String[]{"ohos.permission.DISTRIBUTED_DATASYNC", "ohos.permission.servicebus.ACCESS_SERVICE", "com.huawei.hwddmp.servicebus.BIND_SERVICE"}, 0); super.onStart(intent); }}
Button btn1 = (Button) findComponentById(ResourceTable.Id_migration_btn_01);btn1.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { // 调用DeviceManager的getDeviceList接口,通过FLAG_GET_ONLINE_DEVICE标记获得在线设备列表 List<DeviceInfo> onlineDevices = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE); // 判断组网设备是否为空 if (onlineDevices.isEmpty()) { return; } int numDevices = onlineDevices.size(); ArrayList<String> deviceIds = new ArrayList<>(numDevices); ArrayList<String> deviceNames = new ArrayList<>(numDevices); onlineDevices.forEach((device) -> { deviceIds.add(device.getDeviceId()); deviceNames.add(device.getDeviceName()); }); // 我们这里只有两个设备,所以选择首个设备作为目标设备 // 开发者也可按照具体场景,通过别的方式进行设备选择 String selectDeviceId = deviceIds.get(0); //获取设备ID,最好放到工具类里,很多地方要用! if(selectDeviceId!=null){ Intent intent2 = new Intent(); Operation operation = new Intent.OperationBuilder() .withDeviceId(selectDeviceId) .withBundleName("cn.ybzy.hmsdemo") .withAbilityName("cn.ybzy.hmsdemo.RemoteAbility") .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE) .build(); intent2.setOperation(operation); // 通过AbilitySlice包含的startAbility接口实现跨设备启动FA startAbility(intent2); } }});
06
实现业务在设备间无缝迁移
实战将设备 A 运行时的 FA 迁移到设备 B,实现业务在设备间无缝迁移。
public class MigrationAbility extends Ability implements IAbilityContinuation { @Override public void onStart(Intent intent) { super.onStart(intent); super.setMainRoute(MigrationAbilitySlice.class.getName()); } @Override public boolean onStartContinuation() { return true; } @Override public boolean onSaveData(IntentParams intentParams) { return true; } @Override public boolean onRestoreData(IntentParams intentParams) { return true; } @Override public void onCompleteContinuation(int i) { }}
public class MigrationAbilitySlice extends AbilitySlice implements IAbilityContinuation { TextField textField; String textStr = "请输入数据..."; @Override public void onStart(Intent intent) { super.onStart(intent); super.setUIContent(ResourceTable.Layout_ability_migration); textField = (TextField)findComponentById(ResourceTable.Id_textfield_migration); textField.setText(textStr); Button btn = (Button) findComponentById(ResourceTable.Id_migration_button); btn.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { String deviceId = getDeviceId(); if(deviceId!=null){ continueAbility(deviceId); } } }); } private String getDeviceId(){ // 调用DeviceManager的getDeviceList接口,通过FLAG_GET_ONLINE_DEVICE标记获得在线设备列表 List<DeviceInfo> onlineDevices = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE); // 判断组网设备是否为空 if (onlineDevices.isEmpty()) { return null; } int numDevices = onlineDevices.size(); ArrayList<String> deviceIds = new ArrayList<>(numDevices); ArrayList<String> deviceNames = new ArrayList<>(numDevices); onlineDevices.forEach((device) -> { deviceIds.add(device.getDeviceId()); deviceNames.add(device.getDeviceName()); }); // 我们这里只有两个设备,所以选择首个设备作为目标设备 // 开发者也可按照具体场景,通过别的方式进行设备选择 String selectDeviceId = deviceIds.get(0); return selectDeviceId; } @Override public boolean onStartContinuation() { return true; } @Override public boolean onSaveData(IntentParams intentParams) { intentParams.setParam("data",textField.getText()); return true; } @Override public boolean onRestoreData(IntentParams intentParams) { textStr = intentParams.getParam("data").toString(); return true; } @Override public void onCompleteContinuation(int i) { } @Override public void onRemoteTerminated() { }}
此外,不同于启动行为,FA 的迁移还涉及到状态数据的传递。为此,继承的 IAbilityContinuation 接口为开发者提供迁移过程中特定事件的管理能力。通过自定义迁移事件相关的行为,最终实现对 Ability 的迁移。
主要以较为常用的两个事件,包括迁移发起端完成迁移的回调 onCompleteContinuation(int result) 以及接收到远端迁移行为传递数据的回调 onRestoreData(IntentParams restoreData)。
其他还包括迁移到远端设备的 FA 关闭的回调 onRemoteTerminated()、用于本地迁移发起时保存状态数据的回调 onSaveData(IntentParams saveData)和本地发起迁移的回调 onStartContinuation()。
07
请求回迁
Button btn1 = (Button) findComponentById(ResourceTable.Id_migration_button_back);btn1.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { String deviceId = DeviceUtils.getDeviceId(); if(deviceId!=null){ continueAbilityReversibly(deviceId); //可撤回迁移 } }});Button btn2 = (Button) findComponentById(ResourceTable.Id_migration_button_back2);btn2.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { reverseContinueAbility(); //撤回迁移 }});
①设备 A 上的 Page 请求回迁。
②系统回调设备 B 上 Page 及其 AbilitySlice 栈中所有 AbilitySlice 实例的 IAbilityContinuation.onStartContinuation() 方法,以确认当前是否可以立即迁移。
③如果可以立即迁移,则系统回调设备 B 上 Page 及其 AbilitySlice 栈中所有 AbilitySlice 实例的 IAbilityContinuation.onSaveData() 方法,以便保存回迁后恢复状态必须的数据。
④如果保存数据成功,则系统在设备 A 上 Page 恢复 AbilitySlice 栈,然后回调 IAbilityContinuation.onRestoreData() 方法,传递此前保存的数据。
⑤如果数据恢复成功,则系统终止设备 B 上 Page 的生命周期。

专注开源技术,共建鸿蒙生态

点“阅读原文”了解更多




