重点撸代码:
1、跨设备启动FA、跨设备迁移、回迁
(1)权限
- ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE:用于允许监听分布式组网内的设备状态变化。
- ohos.permission.GET_DISTRIBUTED_DEVICE_INFO:用于允许获取分布式组网内的设备列表和设备信息。
- ohos.permission.GET_BUNDLE_INFO:用于查询其他应用的信息。
- ohos.permission.DISTRIBUTED_DATASYNC:用于允许不同设备间的数据交换。
- "reqPermissions": [
- {"name": "ohos.permission.DISTRIBUTED_DATASYNC"},
- {"name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE"},
- {"name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO" },
- {"name": "ohos.permission.GET_BUNDLE_INFO"}
- ]
- //主动申明,要多设备协同,让用户选择允许还是禁止
- requestPermissionsFromUser(new String[]{"ohos.permission.DISTRIBUTED_DATASYNC"}, 0);
(2)界面:ability_main.xml
- <?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:main_start_fa_btn"
- 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:main_migration_btn"
- 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"
- />
- </DirectionalLayout>
button_bg.xml
- <?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>
另外我们需要的Page Abiltiy:MigrationAbility、RemoveAbility
MainAbilitySlice:
- public class MainAbilitySlice extends AbilitySlice {
- private Button mainStartFABtn,mainMigrationBtn;
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- super.setUIContent(ResourceTable.Layout_ability_main);
- mainStartFABtn = (Button)findComponentById(ResourceTable.Id_main_start_fa_btn);
- mainMigrationBtn = (Button)findComponentById(ResourceTable.Id_main_migration_btn);
- mainStartFABtn.setClickedListener(mClickListener);
- mainMigrationBtn.setClickedListener(mClickListener);
- }
- private Component.ClickedListener mClickListener = new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- int compoentId = component.getId();
- switch (compoentId){
- case ResourceTable.Id_main_start_fa_btn:
- //点击后跨设备打开Fa
- //第一种写法
- Intent intent = new Intent();
- Operation op = new Intent.OperationBuilder()
- .withDeviceId(Common.getOnLineDeviceId())
- .withBundleName("com.ybzy.demo")
- .withAbilityName("com.ybzy.demo.RemoveAbility")
- .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
- .build();
- intent.setOperation(op);
- intent.setParam("msg","我夸设备把你这个FA拉起来了!");
- startAbility(intent);
- //第二钟写法
- intent.setElement(new ElementName(Common.getOnLineDeviceId()
- ,"com.ybzy.demo","com.ybzy.demo.RemoveAbility"));
- intent.setFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE);
- intent.setParam("msg","我夸设备把你这个FA拉起来了!");
- startAbility(intent);
- break;
- case ResourceTable.Id_main_migration_btn:
- //点击后进入要迁移的Ability页面
- Intent migrationIntent = new Intent();
- migrationIntent.setElement(new ElementName("","com.ybzy.demo"
- ,"com.ybzy.demo.MigrationAbility"));
- startAbility(migrationIntent);
- break;
- default:
- break;
- }
- }
- };
- @Override
- public void onActive() {
- super.onActive();
- }
- @Override
- public void onForeground(Intent intent) {
- super.onForeground(intent);
- }
- }
ability_migration.xml
- <?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:migration_text"
- 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:migration_textfield"
- 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_migration_btn"
- 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"
- />
- <Button
- ohos:id="$+id:migration_migration_back_btn"
- 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>
RemoveAbility...把接收到的值显示到页面就行,setText()
(3)工具类
- public class Common{
- public static String getDeviceId(){
- List<DeviceInfo> deviceList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
- if(deviceList.isEmpty()){
- return null;
- }
- int deviceNum = deviceList.size();
- List<String> deviceIds = new ArrayList<>(deviceNum);
- List<String> deviceNames = new ArrayList<>(deviceNum);
- deviceList.forEach((device)->{
- deviceIds.add(device.getDeviceId());
- deviceNames.add(device.getDeviceName());
- });
- //我们这里的实验环境,就两部手机,组件还没讲
- //我就直接使用deviceIds的第一个元素,做为启动远程设备的目标id
- String devcieIdStr = deviceIds.get(0);
- return devcieIdStr;
- }
- public static void myShowTip(Context context,String msg){
- //提示框的核心组件文本
- Text text = new Text(context);
- text.setWidth(MATCH_CONTENT);
- text.setHeight(MATCH_CONTENT);
- text.setTextSize(16, Text.TextSizeType.FP);
- text.setText(msg);
- text.setPadding(30,20,30,20);
- text.setMultipleLine(true);
- text.setMarginLeft(30);
- text.setMarginRight(30);
- text.setTextColor(Color.WHITE);
- text.setTextAlignment(TextAlignment.CENTER);
- //给上面的文本设置一个背景样式
- ShapeElement style = new ShapeElement();
- style.setShape(ShapeElement.RECTANGLE);
- style.setRgbColor(new RgbColor(77,77,77));
- style.setCornerRadius(15);
- text.setBackground(style);
- //构建存放上面的text的布局
- DirectionalLayout mainLayout = new DirectionalLayout(context);
- mainLayout.setWidth(MATCH_PARENT);
- mainLayout.setHeight(MATCH_CONTENT);
- mainLayout.setAlignment(LayoutAlignment.CENTER);
- mainLayout.addComponent(text);
- //最后要让上面的组件绑定dialog
- ToastDialog toastDialog = new ToastDialog(context);
- toastDialog.setSize(MATCH_PARENT,MATCH_CONTENT);
- toastDialog.setDuration(1500);
- toastDialog.setAutoClosable(true);
- toastDialog.setTransparent(true);
- toastDialog.setAlignment(LayoutAlignment.CENTER);
- toastDialog.setComponent((Component) mainLayout);
- toastDialog.show();
- }
- }
(4)实现功能
MigrationAbilitySlice:
- public class MigrationAbilitySlice extends AbilitySlice implements IAbilityContinuation {
- TextField migrationTextField;
- Button migrationMigrationBtn,migrationMigrationBackBtn;
- String msg = "";
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- super.setUIContent(ResourceTable.Layout_ability_migration);
- migrationTextField = (TextField)findComponentById(ResourceTable.Id_migration_textfield);
- migrationTextField.setText(msg);
- migrationMigrationBtn = (Button)findComponentById(ResourceTable.Id_migration_migration_btn);
- migrationMigrationBtn.setClickedListener(component -> {
- //1、要进行迁移的Ability实现接口 IAbilityContinuation,实现的方法返回值改成true
- //2、要进行迁移的Ability下面关联的所有AbilitySlice都要实现接口 IAbilityContinuation,
- // 实现的方法上处理数据
- //3、进行迁移
- String deviceId = Common.getOnLineDeviceId();
- if(deviceId != null){
- // continueAbility(deviceId);
- continueAbilityReversibly(deviceId);
- }
- });
- migrationMigrationBackBtn = (Button) findComponentById(ResourceTable
- .Id_migration_migration_back_btn);
- migrationMigrationBackBtn.setClickedListener(component -> {
- reverseContinueAbility();
- });
- }
- @Override
- public void onActive() {
- super.onActive();
- }
- @Override
- public void onForeground(Intent intent) {
- super.onForeground(intent);
- }
- @Override
- public boolean onStartContinuation() {
- return true;
- }
- @Override
- public boolean onSaveData(IntentParams intentParams) {
- intentParams.setParam("msg",migrationTextField.getText());
- return true;
- }
- @Override
- public boolean onRestoreData(IntentParams intentParams) {
- msg = intentParams.getParam("msg").toString();
- // getUITaskDispatcher().asyncDispatch(() -> {
- // migrationTextField.setText(intentParams.getParam("msg").toString());
- // });
- return true;
- }
- @Override
- public void onCompleteContinuation(int i) {
- }
- }
2、跨设备连接Service
启动远程设备Service的代码示例如下:
添加按钮:
- <Button
- ohos:id="$+id:main_start_removeService_btn"
- ohos:height="match_content"
- ohos:width="300vp"
- ohos:text="远程启动ServiceAbility"
- 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:main_stop_removeService_btn"
- ohos:height="match_content"
- ohos:width="300vp"
- ohos:text="远程关闭ServiceAbility"
- 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"
- />
新建RemoteServiceAbility:
- public class RemoteServiceAbility extends Ability {
- private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");
- private Source sVideoSource;
- private Player sPlayer;
- @Override
- public void onStart(Intent intent) {
- HiLog.error(LABEL_LOG, "RmoteServiceAbility::onStart");
- super.onStart(intent);
- Common.myShowTip(this,"remote onstart");
- sPlayer = new Player(RemoteServiceAbility.this);
- new PlayerThread().start();
- }
- class PlayerThread extends Thread {
- @Override
- public void run() {
- try {
- File mp3FilePath = getExternalFilesDir(Environment.DIRECTORY_MUSIC);
- if (!mp3FilePath.exists()) {
- mp3FilePath.mkdirs();
- }
- File mp3File = new File(mp3FilePath.getAbsolutePath() + "/" + "bj.mp3");
- Resource res = getResourceManager()
- .getRawFileEntry("resources/rawfile/bj.mp3").openRawFile();
- byte[] buf = new byte[4096];
- int count = 0;
- FileOutputStream fos = new FileOutputStream(mp3File);
- while ((count = res.read(buf)) != -1) {
- fos.write(buf, 0, count);
- }
- FileDescriptor fileDescriptor = new FileInputStream(mp3File).getFD();
- sVideoSource = new Source(fileDescriptor);
- sPlayer.setSource(sVideoSource);
- sPlayer.prepare();
- sPlayer.setVolume(0.3f);
- sPlayer.enableSingleLooping(true);
- sPlayer.play();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- @Override
- public void onStop() {
- super.onStop();
- Common.myShowTip(RemoteServiceAbility.this,"remote onStop");
- sPlayer.stop();
- }
- @Override
- public IRemoteObject onConnect(Intent intent) {
- return null;
- }
- @Override
- public void onDisconnect(Intent intent) {
- }
- }
启动:
- case ResourceTable.Id_main_start_remoteService_btn:
- Common.myShowTip(MainAbilitySlice.this,deviceId);
- Intent startRemoteServiceIntent = new Intent();
- startRemoteServiceIntent.setElement(new ElementName(
- deviceId,
- "com.ybzy.demo",
- "com.ybzy.demo.RemoteServiceAbility"
- ));
- startRemoteServiceIntent.setFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE);
- startAbility(startRemoteServiceIntent);
- break;
关闭远程设备Service:
- case ResourceTable.Id_main_stop_remoteService_btn:
- Common.myShowTip(MainAbilitySlice.this,deviceId);
- Intent stopRemoteServiceIntent = new Intent();
- stopRemoteServiceIntent.setElement(new ElementName(
- deviceId,
- "com.ybzy.demo",
- "com.ybzy.demo.RemoteServiceAbility"
- ));
- stopRemoteServiceIntent.setFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE);
- stopAbility(stopRemoteServiceIntent);
- break;
仅通过启动和停止Service Ability两种方式对Service进行调度无法应对需长期交互的场景,
简单地说,信息就是只能去,回不来!
因此,分布式任务调度平台向开发者提供了跨设备Service连接及断开连接的能力。
链接上了,信息可去可回!
链接是使用connectAbility()方法,需要传入目标Service的Intent与接口IAbilityConnection的实例对象。
接口IAbilityConnection提供了两个方法供开发者实现:
(1)onAbilityConnectDone()用来处理连接的回调。
(2)onAbilityDisconnectDone()用来处理断开连接的回调。
我们可以在onAbilityConnectDone()中获取管理链接的代理,进一步为了使用该代理跨设备调度Service,
开发者需要在本地及远端分别实现对外接口一致的代理,这个接口是IRemoteBroker。
添加按钮:
- <Button
- ohos:id="$+id:main_connect_remoteService_btn"
- ohos:height="match_content"
- ohos:width="300vp"
- ohos:text="远程链接ServiceAbility"
- 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:main_use_remoteService_btn"
- ohos:height="match_content"
- ohos:width="300vp"
- ohos:text="使用远程ServiceAbility"
- 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:main_disconnect_remoteService_btn"
- ohos:height="match_content"
- ohos:width="300vp"
- ohos:text="远程断开ServiceAbility"
- 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"
- />
发起连接的本地侧的代理示例如下:
- public class MyRemoteProxy implements IRemoteBroker {
- //IRemoteBroker:获取远程代理对象的持有者
- private static final int ERR_OK = 0;
- //COMMAND_PLUS表示有效消息进行通信的约定的标记,MIN_TRANSACTION_ID是这个标记可以用的最小值:1
- private static final int COMMAND_PLUS = IRemoteObject.MIN_TRANSACTION_ID;
- //IRemoteObject:此接口
- // 可用于查询或获取接口描述符、
- // 添加或删除死亡通知、
- // 将对象状态转储到特定文件以及发送消息。
- 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:这个类提供了读写对象、接口标记、文件描述符和大数据的方法。
- MessageParcel data = MessageParcel.obtain();
- //obtain()创建索引为0的空MessageParcel对象
- MessageParcel reply = MessageParcel.obtain();
- //MessageOption:定义与sendRequest一起发送消息的选项。
- // option不同的取值,决定采用同步或异步方式跨设备调用Service
- // 这个例子我们需要同步获取对端Service执行加法运算后的结果,同步模式调用sendRequest接口,即MessageOption.TF_SYNC
- // 对应的是异步:TF_ASYNC
- MessageOption option = new MessageOption(MessageOption.TF_SYNC);
- data.writeInt(a);
- data.writeInt(b);
- try {
- remote.sendRequest(COMMAND_PLUS, data, reply, option);
- //第1个参数:约定通信双方确定的消息标记。
- //第2个参数:发送到对等端侧的数据包裹MessageParcel对象。
- //第3个参数:对等端侧返回的数据包裹MessageParcel对象。
- //第4个参数:设置发送消息,用同步还是异步模式。
- int ec = reply.readInt(); //返回通信成不成功,约定的标记ERR_OK
- if (ec != ERR_OK) {
- throw new RemoteException();
- }
- int result = reply.readInt();
- return result;
- } catch (RemoteException e) {
- throw new RemoteException();
- } finally {
- data.reclaim(); //reclaim()清除不再使用的MessageParcel对象。
- reply.reclaim();
- }
- }
- }
等待连接的远端侧的代理示例如下:
- 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;
- }
- }
等待连接侧还需要作如下修改:
- // 绑定前面定义的代理,实例化出发起链接侧需要的代理
- private MyRemote remote = new MyRemote();
- @Override
- public IRemoteObject onConnect(Intent intent) {
- //链接成功的时候,给发起链接侧返回去
- return remote.asObject();
- }
完成上述步骤后,可以通过点击事件实现连接、利用连接关系控制PA以及断开连接等行为,代码示例如下:
- private MyRemoteProxy mProxy = null;
- // 创建连接回调实例
- private IAbilityConnection conn = new IAbilityConnection() {
- // 连接到Service的回调
- @Override
- public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {
- // 在这里开发者可以拿到服务端传过来IRemoteObject对象,从中解析出服务端传过来的信息
- mProxy = new MyRemoteProxy(iRemoteObject);
- UIUtils.showTip(MainAbilitySlice.this,"拿到remoteObject:" + mProxy);
- }
- // 意外断开连接才会回调
- @Override
- public void onAbilityDisconnectDone(ElementName elementName, int resultCode) {
- }
- };
- // 连接远程
- case ResourceTable.Id_main_connect_remoteService_btn:
- //1、实现连接的本地侧的代理
- //2、实现等待连接的远端侧的代理
- //3、修改等待连接侧的Service
- //4、在本地(发起链接侧)获取远端(被链接侧)返回过来的链接代理,创建链接后的回调函数
- //5、实现链接,通过代理对象使用Service的服务
- if (deviceId != null) {
- Intent connectServiceIntent = new Intent();
- connectServiceIntent.setElement(new ElementName(
- deviceId,
- "com.ybzy.demo",
- "com.ybzy.demo.RemoteServiceAbility"
- ));
- connectServiceIntent.setFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE);
- connectAbility(connectServiceIntent, iAbilityConnection);
- }
- break;
- // 链接后使用
- case ResourceTable.Id_main_use_remoteService_btn:
- if (mProxy != null) {
- int ret = -1;
- try {
- ret = mProxy.plus(10, 20);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- Common.myShowTip(MainAbilitySlice.this, "获取的结果:" + ret);
- }
- break;
- // 用完断开
- case ResourceTable.Id_main_disconnect_remoteService_btn:
- disconnectAbility(iAbilityConnection);
- break;