0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

鸿蒙软总线跨设备访问解析

鸿蒙系统HarmonyOS 来源:51CTO 作者:zhonghongfa 2021-04-16 14:44 次阅读

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

另外我们需要的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


RemoveAbility...把接收到的值显示到页面就行,setText()

(3)工具类

public class Common{
    public static String getDeviceId(){
        List deviceList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
        if(deviceList.isEmpty()){
            return null;
        }
        int deviceNum = deviceList.size();
        List deviceIds = new ArrayList<>(deviceNum);
        List 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的代码示例如下: 添加按钮:

新建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。 添加按钮:

发起连接的本地侧的代理示例如下:

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;

编辑:hfy

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 鸿蒙系统
    +关注

    关注

    183

    文章

    2629

    浏览量

    65277
收藏 人收藏

    评论

    相关推荐

    华为鸿蒙—时代的开启者!

    将适配手机、平板、电脑、智能汽车、可穿戴设备等多终端设备。下面来说下华为鸿蒙OS的四大特性。第一,分布式架构首次用在终端OS上,实现跨越端无缝协同体验。鸿蒙OS的“分布式OS架构”和“
    发表于 09-03 11:19

    如何理解鸿蒙OS是设备的?

    谁能帮忙解释鸿蒙OS是怎样实现平台的?
    发表于 09-08 18:17

    [文章] HarmonyOS学习之一:鸿蒙HarmonyOS系统介绍

    的要求。鸿蒙系统具有硬件互助、资源共享等特征,记忆体体现在下面几个方面:分布式总线分布式总线是多种终端
    发表于 11-26 15:26

    [文章] HarmonyOS学习之一:鸿蒙HarmonyOS系统介绍

    的要求。鸿蒙系统具有硬件互助、资源共享等特征,记忆体体现在下面几个方面:分布式总线分布式总线是多种终端
    发表于 12-02 16:57

    科普干货|谈谈鸿蒙LiteOS-M与HUAWEI LiteOS内核的几大不同之处

    分布式数据管理基于分布式总线的能力,实现应用程序数据和用户数据的分布式管理。用户数据不再与单一物理设备绑定,业务逻辑与数据存储分离,应用设备
    发表于 12-11 10:42

    如何基于分布式总线进行“三步走”极简开发

    一、什么是分布式总线呢?分布式总线是HarmonyOS架构中最底层的技术分布式总线是Har
    发表于 12-24 10:43

    #2020征文-手机#【鸿蒙基地】鸿蒙设备启动窗口:Page Ability

    HarmonyOS的核心特性(或称为卖点)之一就是总线技术,而Page Ability的设备迁移是
    发表于 12-31 11:56

    华为鸿蒙系统 HarmonyOS 2.0 京东、美团、优酷等软件,分布式设备交互演示及简单说明

    华为鸿蒙系统 HarmonyOS 2.0京东、美团、优酷等软件,分布式设备交互演示及简单说明
    发表于 01-14 09:52

    #2020征文-手机#【鸿蒙基地】鸿蒙设备启动窗口:Page Ability

    HarmonyOS的核心特性(或称为卖点)之一就是总线技术,而Page Ability的设备迁移是
    发表于 01-25 14:13

    通动力鸿蒙生态建设再进一步 分布式技术抢先体验

    近日,通动力鸿蒙生态研发团队在智能终端新交互体验、智能设备应用场景、操作系统第三方组件的研究和开发方面,有了突破性的进展。   通动力鸿蒙
    发表于 03-10 15:46

    分布式总线子系统

    分布式总线子系统简介目录约束使用涉及仓简介设备通信方式多种多样(USB/WIFI/BT等),不同通信方式使用差异很大且繁琐,同时通信链路的融合共享和冲突无法处理,通信安全问题也不好保证。本项
    发表于 04-23 17:12

    深度解读设备的“万能语言”鸿蒙系统的分布式总线能力 精选资料推荐

    摘要:本文分享鸿蒙分布式总线,并对相关源代码进行解析,为在鸿蒙系统平台上工作的相关人员的信息参考和指导。
    发表于 07-21 06:27

    鸿蒙总线的简单使用

    鸿蒙总线的简单使用-HiHope社区官方号-电子发烧友网 (elecfans.com)
    发表于 08-18 11:02

    5分钟深度解析鸿蒙基础架构,附原文档!

    。3,分布式数据管理分布式数据管理基于分布式总线的能力,实现应用程序数据和用户数据的分布式管理。用户数据不再与单一物理设备绑定,业务逻辑与数据存储分离,应用
    发表于 11-12 10:29

    你玩过轻量系统总线应用吗?

    Provider的能力。Provider:服务的提供者,为系统提供能力(对外接口)。PRC服务注册RPC:(Remote Procedure Call)用于设备跨进程间的通信,在轻量系统
    发表于 07-14 10:42