一、概述
① 分布式任务调度
- 在 HarmonyOS 中,分布式任务调度平台对搭载 HarmonyOS 的多设备构筑的“超级虚拟终端”提供统一的组件管理能力,为应用定义统一的能力基线、接口形式、数据结构、服务描述语言,屏蔽硬件差异;支持远程启动、远程调用、业务无缝迁移等分布式任务。
- 分布式任务调度平台在底层实现 Ability(分布式任务调度的基本组件)跨设备的启动/关闭、连接及断开连接以及迁移等能力,实现跨设备的组件管理:
- 启动和关闭:向开发者提供管理远程 Ability 的能力,即支持启动 Page 模板的 Ability,以及启动、关闭 Service 和 Data 模板的 Ability。
- 连接和断开连接:向开发者提供跨设备控制服务(Service 和 Data 模板的 Ability)的能力,开发者可以通过与远程服务连接及断开连接实现获取或注销跨设备管理服务的对象,达到和本地一致的服务调度。
- 迁移能力:向开发者提供跨设备业务的无缝迁移能力,开发者可以通过调用 Page 模板 Ability 的迁移接口,将本地业务无缝迁移到指定设备中,打通设备间壁垒。
② 约束与限制
- 开发者需要在 Intent 中设置支持分布式的标记(例如: Intent.FLAG_ABILITYSLICE_MULTI_DEVICE 表示该应用支持分布式调度),否则将无法获得分布式能力。
- 开发者通过在 config.json 中的 reqPermissions 字段里添加多设备协同访问的权限申请:三方应用使用{“name”: “ohos.permission.DISTRIBUTED_DATASYNC”}。
- PA(Particle Ability,Service 和 Data 模板的 Ability)的调用支持连接及断开连接、启动及关闭这四类行为,在进行调度时:
-
- 开发者必须在 Intent 中指定 PA 对应的 bundleName 和 abilityName。
-
- 当开发者需要跨设备启动、关闭或连接 PA 时,需要在 Intent 中指定对端设备的 deviceId。开发者可通过如设备管理类 DeviceManager 提供的 getDeviceList 获取指定条件下匿名化处理的设备列表,实现对指定设备 PA 的启动/关闭以及连接管理。
- FA(Feature Ability,Page 模板的 Ability)的调用支持启动和迁移行为,在进行调度时:
-
- 当启动 FA 时,需要开发者在 Intent 中指定对端设备的 deviceId、bundleName 和 abilityName。
-
- FA 的迁移实现相同 bundleName 和 abilityName 的 FA 跨设备迁移,因此需要指定迁移设备的 deviceId。
- DevEco Studio 远程模拟设备的功能无法调测分布式任务调度,需要在真机环境下进行测试。
二、场景介绍
- 开发者在应用中集成分布式调度能力,通过调用指定能力的分布式接口,实现跨设备能力调度。根据 Ability 模板及意图的不同,分布式任务调度向开发者提供以下六种能力:启动远程 FA、启动远程 PA、关闭远程 PA、连接远程 PA、断开连接远程 PA 和 FA 跨设备迁移。
- 下面以设备 A(本地设备)和设备 B(远端设备)为例,进行场景介绍:
-
- 设备 A 启动设备 B 的 FA:在设备 A 上通过本地应用提供的启动按钮,启动设备 B 上对应的 FA。例如:设备 A 控制设备 B 打开相册,只需开发者在启动 FA 时指定打开相册的意图即可。
-
- 设备 A 启动设备 B 的 PA:在设备 A 上通过本地应用提供的启动按钮,启动设备 B 上指定的 PA。例如:开发者在启动远程服务时通过意图指定音乐播放服务,即可实现设备 A 启动设备 B 音乐播放的能力。
-
- 设备 A 关闭设备 B 的 PA:在设备 A 上通过本地应用提供的关闭按钮,关闭设备 B 上指定的 PA。类似启动的过程,开发者在关闭远程服务时通过意图指定音乐播放服务,即可实现关闭设备 B 上该服务的能力。
-
- 设备 A 连接设备 B 的 PA:在设备 A 上通过本地应用提供的连接按钮,连接设备 B 上指定的 PA。连接后,通过其他功能相关按钮实现控制对端 PA 的能力。通过连接关系,开发者可以实现跨设备的同步服务调度,实现如大型计算任务互助等价值场景。
-
- 设备 A 与设备 B 的 PA 断开连接:在设备 A 上通过本地应用提供断开连接的按钮,将之前已连接的 PA 断开连接。
-
- 设备 A 的 FA 迁移至设备 B:设备 A 上通过本地应用提供的迁移按钮,将设备 A 的业务无缝迁移到设备 B 中。通过业务迁移能力,打通设备 A 和设备 B 间的壁垒,实现如文档跨设备编辑、视频从客厅到房间跨设备接续播放等场景。
三、API 说明
- 分布式调度平台提供的连接和断开连接 PA、启动远程 FA、启动和关闭 PA 以及迁移 FA 的能力,是实现更多价值性场景的基础。
① 连接远程 PA
- connectAbility(Intent intent, IAbilityConnection conn)接口提供连接指定设备上 PA 的能力,Intent 中指定待连接 PA 的设备 deviceId、bundleName 和 abilityName。
- 当连接成功后,通过在 conn 定义的 onAbilityConnectDone 回调中获取对端 PA 的服务代理,两者的连接关系则由 conn 维护。具体的参数定义如下表所示:
参数名 |
类型 |
说明 |
intent |
ohos.aafwk.content.Intent |
开发者需在intent对应的Operation中指定待连接PA的设备deviceId、bundleName和abilityName |
conn |
ohos.aafwk.ability.IAbilityConnection |
当连接成功或失败时,作为连接关系的回调接口。该接口提供连接完成和断开连接完成时的处理逻辑,开发者可根据具体的场景进行定义 |
② 启动远程 FA/PA
- startAbility(Intent intent)接口提供启动指定设备上 FA 和 PA 的能力,Intent 中指定待启动 FA/PA 的设备 deviceId、bundleName 和 abilityName。
- 具体参数定义如下表所示:
参数名 |
类型 |
说明 |
intent |
ohos.aafwk.content.Intent |
当开发者需要调用该接口启动远程PA时,需要指定待启动PA的设备deviceId、bundleName和abilityName。若不指定设备deviceId,则无法跨设备调用PA。类似地,在启动FA时,也需要开发者指定启动FA的设备deviceId、bundleName和abilityName |
- 分布式调度平台还会提供与上述功能相对应的断开远程 PA 的连接和关闭远程 PA 的接口,相关的参数与连接、启动的接口类似。
-
- 断开远程 PA 连接:disconnectAbility(IAbilityConnection conn)。
-
- 关闭远程 PA:stopAbility(Intent intent)。
③ 迁移 FA
- continueAbility(String deviceId) 接口提供将本地FA迁移到指定设备上的能力,需要开发者在调用时指定目标设备的 deviceId。具体参数定义如下表所示:
参数名 |
类型 |
说明 |
deviceId |
String |
当开发者需要调用该接口将本地FA迁移时,需要指定目标设备的deviceId |
- Ability 和 AbilitySlice 类均需要实现 IAbilityContinuation 及其方法,才可以实现 FA 迁移。
四、开发流程
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.ability.IAbilityConnection;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.bundle.ElementName;
import ohos.aafwk.ability.IAbilityContinuation;
import ohos.aafwk.content.IntentParams;
import ohos.rpc.IRemoteObject;
import ohos.rpc.IRemoteBroker;
import ohos.rpc.MessageParcel;
import ohos.rpc.MessageOption;
import ohos.rpc.RemoteException;
import ohos.rpc.RemoteObject;
import ohos.distributedschedule.interwork.DeviceInfo;
import ohos.distributedschedule.interwork.DeviceManager;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Component.ClickedListener;
import ohos.agp.components.ComponentContainer.LayoutConfig;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.components.PositionLayout;
public class SampleSlice extends AbilitySlice {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
PositionLayout layout = new PositionLayout(this);
LayoutConfig config = new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT);
layout.setLayoutConfig(config);
ShapeElement buttonBg = new ShapeElement();
buttonBg.setRgbColor(new RgbColor(0, 125, 255));
addComponents(layout, buttonBg, config);
super.setUIContent(layout);
}
@Override
public void onInactive() {
super.onInactive();
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onBackground() {
super.onBackground();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
@Override
public void onStop() {
super.onStop();
}
}
- 使用分布式能力要求开发者在 Ability 对应的 config.json 中声明多设备协同访问的权限:三方应用使用{“name”: “ohos.permission.DISTRIBUTED_DATASYNC”}。一个三方应用部署的示例如下:
{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
},
{
...
}
]
}
}
- 对于三方应用还要求在实现 Ability 的代码中显式声明需要使用的权限,如下所示:
public class SampleSlice extends AbilitySlice {
@Override
public void onStart(Intent intent) {
requestPermissionsFromUser(new String[]{"ohos.permission.DISTRIBUTED_DATASYNC"}, 0);
super.onStart(intent);
}
}
private void addComponents(PositionLayout linear, ShapeElement buttonBg, LayoutConfig config) {
btnStartRemoteFA = createButton("StartRemoteFA", buttonBg, config);
btnStartRemoteFA.setClickedListener(mStartRemoteFAListener);
linear.addComponent(btnStartRemoteFA);
btnStartRemotePA = createButton("StartRemotePA", buttonBg, config);
btnStartRemotePA.setClickedListener(mStartRemotePAListener);
linear.addComponent(btnStartRemotePA);
btnStopRemotePA = createButton("StopRemotePA", buttonBg, config);
btnStopRemotePA.setClickedListener(mStopRemotePAListener);
linear.addComponent(btnStopRemotePA);
btnConnectRemotePA = createButton("ConnectRemotePA", buttonBg, config);
btnConnectRemotePA.setClickedListener(mConnectRemotePAListener);
linear.addComponent(btnConnectRemotePA);
btnControlRemotePA = createButton("ControlRemotePA", buttonBg, config);
btnControlRemotePA.setClickedListener(mControlPAListener);
linear.addComponent(btnControlRemotePA);
btnDisconnectRemotePA = createButton("DisconnectRemotePA", buttonBg, config);
btnDisconnectRemotePA.setClickedListener(mDisconnectRemotePAListener);
linear.addComponent(btnDisconnectRemotePA);
btnContinueRemoteFA = createButton("ContinueRemoteFA", buttonBg, config);
btnContinueRemoteFA.setClickedListener(mContinueAbilityListener);
linear.addComponent(btnContinueRemoteFA);
}
- 通过设备管理 DeviceManager 提供的 getDeviceList 接口获取设备列表,用于指定目标设备:
interface ISelectResult {
void onSelectResult(String deviceId);
}
private void scheduleRemoteAbility(ISelectResult listener) {
List<DeviceInfo> onlineDevices = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
if (onlineDevices.isEmpty()) {
listener.onSelectResult(null);
return;
}
int numDevices = onlineDevices.size();
List<String> deviceIds = new ArrayList<>(numDevices);
onlineDevices.forEach((device) -> {
deviceIds.add(device.getDeviceId());
});
String selectDeviceId = deviceIds.get(0);
listener.onSelectResult(selectDeviceId);
}
- 上述实例中涉及对在线组网设备的查询,该项能力需要开发者在对应的 config.json 中声明获取设备列表及设备信息的权限,如下所示:
{
"reqPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE"
},
{
"name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO"
},
{
"name": "ohos.permission.GET_BUNDLE_INFO"
}
]
}
- 为启动远程 FA 的按钮设置点击回调,实现启动远程 FA 的能力:
private ClickedListener mStartRemoteFAListener = new ClickedListener() {
@Override
public void onClick(Component arg0) {
scheduleRemoteAbility(new ISelectResult() {
@Override
void onSelectResult(String deviceId) {
if (deviceId != null) {
Operation operation = new Intent.OperationBuilder()
.withDeviceId(deviceId)
.withBundleName(bundleName)
.withAbilityName(abilityName)
.withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
.build();
Intent intent = new Intent();
intent.setOperation(operation);
startAbility(intent);
}
}
});
}
};
- 为启动和关闭 PA 定义回调,实现启动和关闭 PA 的能力。
-
- 对于 PA 的启动、关闭、连接等操作都需要开发者提供目标设备的 deviceId。开发者可以通过 DeviceManager 相关接口得到当前组网下的设备列表,并以弹窗的形式供用户选择,也可以按照实际需要实现其他个性化的处理方式。
-
- 在点击事件回调函数中,需要开发者指定得到 deviceId 后的处理逻辑,即实现类似上例中 listener.onSelectResult(String deviceId) 的方法,代码示例如下:
private ClickedListener mStartRemotePAListener = new ClickedListener() {
@Override
public void onClick(Component arg0) {
scheduleRemoteAbility(new ISelectResult() {
@Override
void onSelectResult(String deviceId) {
if (deviceId != null) {
Operation operation = new Intent.OperationBuilder()
.withDeviceId(deviceId)
.withBundleName(bundleName)
.withAbilityName(abilityName)
.withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
.build();
Intent intentToStartPA = new Intent();
intentToStartPA.setOperation(operation);
startAbility(intentToStartPA);
}
}
});
}
};
private ClickedListener mStopRemotePAListener = new ClickedListener() {
@Override
public void onClick(Component arg0) {
scheduleRemoteAbility(new ISelectResult() {
@Override
void onSelectResult(String deviceId) {
if (deviceId != null) {
Operation operation = new Intent.OperationBuilder()
.withDeviceId(deviceId)
.withBundleName(bundleName)
.withAbilityName(abilityName)
.withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
.build();
Intent intentToStopPA = new Intent();
intentToStopPA.setOperation(operation);
stopAbility(intentToStopPA);
}
}
});
}
};
-
- 启动和关闭的行为类似,只需在 Intent 中指定待调度 PA 的 deviceId、bundleName 和 abilityName,并以 operation 的形式封装到 Intent 内。通过AbilitySlice(Ability)包含的 startAbility() 和 stopAbility() 接口即可实现相应功能。
- 设备 A 连接设备 B 侧的 PA,利用连接关系调用该 PA 执行特定任务,以及断开连接:
private MyRemoteProxy mProxy = null;
private IAbilityConnection mConn = new IAbilityConnection() {
@Override
public void onAbilityConnectDone(ElementName element, IRemoteObject remote, int resultCode) {
mProxy = new MyRemoteProxy(remote);
btnConnectRemotePA.setText("connectRemoteAbility done");
}
@Override
public void onAbilityDisconnectDone(ElementName element, int resultCode) {
disconnectAbility(mConn);
}
};
- 仅通过启动/关闭两种方式对 PA 进行调度无法应对需长期交互的场景,因此,分布式任务调度平台向开发者提供了跨设备 PA 连接及断开连接的能力。
- 为了对已连接 PA 进行管理,开发者需要实现一个满足 IAbilityConnection 接口的连接状态检测实例,通过该实例可以对连接及断开连接完成时设置具体的处理逻辑,例如:获取控制对端PA的代理等。进一步为了使用该代理跨设备调度 PA,开发者需要在本地及对端分别实现对外接口一致的代理。一个具备加法能力的代理示例如下:
public class MyRemoteProxy implements IRemoteBroker {
private static final int ERR_OK = 0;
private static final int COMMAND_PLUS = IRemoteObject.MIN_TRANSACTION_ID;
private final IRemoteObject remote;
public MyRemoteProxy(IRemoteObject remote) {
this.remote = remote;
}
@Override
public IRemoteObject asObject() {
return remote;
}
public int plus(int a, int b) throws RemoteException {
MessageParcel data = MessageParcel.obtain();
MessageParcel reply = MessageParcel.obtain();
MessageOption option = new MessageOption(MessageOption.TF_SYNC);
data.writeInt(a);
data.writeInt(b);
try {
remote.sendRequest(COMMAND_PLUS, data, reply, option);
int errCode = reply.readInt();
if (errCode != ERR_OK) {
throw new RemoteException();
}
int result = reply.readInt();
return result;
}
finally {
data.reclaim();
reply.reclaim();
}
}
}
- 此外,对端待连接的PA需要实现对应的客户端,代码示例如下所示:
public class MyRemote extends RemoteObject implements IRemoteBroker{
private static final int ERR_OK = 0;
private static final int ERROR = -1;
private static final int COMMAND_PLUS = IRemoteObject.MIN_TRANSACTION_ID;
public MyRemote() {
super("MyService_Remote");
}
@Override
public IRemoteObject asObject() {
return this;
}
@Override
public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
if (code != COMMAND_PLUS) {
reply.writeInt(ERROR);
return false;
}
int value1 = data.readInt();
int value2 = data.readInt();
int sum = value1 + value2;
reply.writeInt(ERR_OK);
reply.writeInt(sum);
return true;
}
}
- 对端除了要实现如上所述的客户端外,待连接的 PA 还需要作如下修改:
private MyRemote remote = new MyRemote();
@Override
protected IRemoteObject onConnect(Intent intent) {
super.onConnect(intent);
return remote.asObject();
}
- 完成上述步骤后,可以通过点击事件实现连接、利用连接关系控制 PA 以及断开连接等行为,代码示例如下:
private ClickedListener mConnectRemotePAListener = new ClickedListener() {
@Override
public void onClick(Component arg0) {
scheduleRemoteAbility(new ISelectResult() {
@Override
void onSelectResult(String deviceId) {
if (deviceId != null) {
Intent connectPAIntent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withDeviceId(deviceId)
.withBundleName(bundleName)
.withAbilityName(abilityName)
.withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
.build();
connectPAIntent.setOperation(operation);
connectAbility(connectPAIntent, mConn);
}
}
});
}
};
private ClickedListener mControlPAListener = new ClickedListener() {
@Override
public void onClick(Component arg0) {
if (mProxy != null) {
int ret = -1;
try {
ret = mProxy.plus(10, 20);
} catch (RemoteException e) {
HiLog.error(LABEL, "ControlRemotePA error");
}
btnControlRemotePA.setText("ControlRemotePA result = " + ret);
}
}
};
private ClickedListener mDisconnectRemotePAListener = new ClickedListener() {
@Override
public void onClick(Component arg0) {
btnConnectRemotePA.setText("ConnectRemotePA");
btnControlRemotePA.setText("ControlRemotePA");
disconnectAbility(mConn);
}
};
- 设备 A 将运行时的 FA 迁移到设备 B,实现业务在设备间无缝迁移:
private ClickedListener mContinueAbilityListener = new ClickedListener() {
@Override
public void onClick(Component arg0) {
scheduleRemoteAbility(new ISelectResult() {
@Override
public void onSelectResult(String deviceId) {
continueAbility(deviceId);
}
});
}
};
- FA 的迁移还涉及到状态数据的传递,需要继承 IAbilityContinuation 接口,供开发者实现迁移过程中特定事件的管理能力,代码示例如下:
public class SampleSlice extends AbilitySlice implements IAbilityContinuation {
@Override
public boolean onSaveData(IntentParams saveData) {
String exampleData = String.valueOf(System.currentTimeMillis());
saveData.setParam("continueParam", exampleData);
return true;
}
@Override
public boolean onRestoreData(IntentParams restoreData) {
Object data = restoreData.getParam("continueParam");
return true;
}
@Override
public void onCompleteContinuation(int result) {
btnContinueRemoteFA.setText("ContinueAbility Done");
}
}
- 通过自定义迁移事件相关的行为,最终实现对 Ability 的迁移,此处主要以较为常用的两个事件,包括迁移发起端完成迁移的回调 onCompleteContinuation(int result) 以及接收到远端迁移行为传递数据的回调 onRestoreData(IntentParams restoreData)。其他还包括迁移到远端设备的 FA 关闭的回调 onRemoteTerminated()、用于本地迁移发起时保存状态数据的回调onSaveData(IntentParams saveData)和本地发起迁移的回调onStartContinuation()。
- 按照实际应用自定义特定场景对应的回调,可以完成多种场景下 FA 的迁移任务。FA 迁移可以打通设备间的壁垒,有助于不同能力的设备进行互助。
- FA 迁移过程中,远端 FA 首先接收到发起端 FA 传输的数据,再执行启动,即 onRestoreData() 发生在 onStart() 之前。
五、实例参考
- 演示分布式任务调度的六种场景:启动远程FA,启动远程PA,关闭远程PA,连接远程PA,断开连接远程PA, 和FA跨端迁移:DistributedScheduler。