伤心客 发表于 2024-10-27 03:48:50

HarmonyOS - 实现多设备协同开发实战教程~

前言

现在随着个人设备越来越多,越来越须要多个设备之间相互感知和连接,设备和设备之间可以相互联动,形成互联互通的场景,而搭载HarmonyOS的设备恰恰可以满足这一点 。下面通过开发一个HarmonyOS的多端分布式表明应用来实现设备之间的相互联动。
项目先容

H5页面可以实现一些比较特殊的页面结果,所以选择在应用中集成H5页面。应用可以将页面直接投放到附近其他HarmonyOS设备上,实现多端设备分布式表现,同时应用可以跨端控制,更新应用页面,形成多设备协同的结果。
下面是结果展示:
https://i-blog.csdnimg.cn/blog_migrate/30cb469c8b8e893bc15dac42fca95b11.gif
多设备协同原理

HarmonyOS 给应用开发者提供了一套在多个设备差别应用之间进利用命流转的API接口,实现设备协同须要关注 流转使命管理服务 和 分布式使命调度。
• 流转使命管理服务:在流转发起端,接受用户应用程序注册,提供流转入口、状态表现、退出流转等管理本领。
• 分布式使命调度:提供长途服务启动、长途服务连接、长途迁移等本领,并通过差别本领组合,支持用户应用程序完成跨端迁移或多端协同的业务体验。
• 分布式安全:提供E2E的加密通道,为用户应用程序提供安全的跨端传输机制,保证“正确的人,通过正确的设备,正确地利用数据”。
• 分布式软总线:利用基于手机、平板、智能穿戴、智慧屏平分布式设备的统一通信基座,为设备之间的互联互通提供统一的分布式通信本领。
• 使命流转流程:设备A和设备B登录相同的华为账号,在同一网络下。设备A向设备B发起协同,应用程序需先向系统的流转使命管理服务 注册回调,获取到设备B的 DeviceId 等设备信息,设备A初始化分布式使命调度,通过设备DeviceId指定设备发起协同,设备B接收到协同请求,初始化分布式使命调度,启动对应的应用程序,把流转的状态上报给流转使命管理服务,流转使命管理服务返回流转结果,完成一次设备A到设备B的使命流转。
https://i-blog.csdnimg.cn/blog_migrate/0bc41291c0ca2c268566cccfdb3f4c22.png
理解分布式软总线

• 分布式:指的是一种运行方式,简单来说就是使命可以在一个设备上运行,也可以多个设备连接起来一起运行,在多个设备中没有一个绝对的中央。
• 总线:简单了解一下“总线”的概念,在计算机系统中,各个部件之间传送信息的通道叫总线,外部设备通过相应的接口与总线相连接,组成了整个计算机系统。
• 分布式软总线:所以不难理解分布式软总线其实就是实现多个设备之间的连接,传递消息,实现使命多端运行的一种技术。在HarmonyOS中,底层已经帮我们实现了设备之间的组网、发现和连接,所以并不须要关心设备怎么通信,只须要调用底层封装好的接口,实现多设备协同就可以了。
实现步调

实现分布式多设备协同,须要实现跨端启动应用、后台PA服务、分布式数据同步的功能,详细实现流程如下
https://i-blog.csdnimg.cn/blog_migrate/3135ba7f4c9b1e39bab77f9c1fcceb6a.png
一、跨设备启动应用

多设备协同实现的前提,须要在多端安装相同的应用,而在现实利用环境中,在多个设备中安装一个相同的应用还是一个比较麻烦的事。而HarmonyOS的原子化服务则不须要用户手动安装,由系统程序框架后台安装后即可利用,在HarmonyOS的服务中央以服务卡片的形式展示。
应用由原子化服务平台(Huawei Ability Gallery)管理和分发,只须要上传到原子化服务平台(Huawei Ability Gallery)即可,在多设备协同中,当设备A的应用向设备B的应用发起多端协同,假如设备B上没有安装对应服务,HarmonyOS会自动下载相干原子化服务,和A端的应用一起进行多端协同。
跨设备启动应用,也就是设备A上的应用可以拉起设备B上的应用。因为原子化服务应用免安装的特性,所以不消关心应用在多设备上的安装,只需实现跨设备启动应用即可。
1. 创建原子化服务

以原子化服务的形式创建项目, 原子化服务的特点是支持免安装,没有应用图标,只在 HarmanoyOS 服务中央以卡片的形式展现,支持跨端迁移和多端协同。
在新建项目时选择Atomic Service,创建一个原子化服务,同时打开Show in service center 开关,自动创建服务卡片,去掉TV的勾选状态,目前服务卡片不支持TV设备。
https://i-blog.csdnimg.cn/blog_migrate/e146b06b4b18ea52e8f6cc850a3d795a.png
2. 创建HarmonyOS IDL接口

选择项目的module目次,点击鼠标右键,选择New>Idl File,如下图:
https://i-blog.csdnimg.cn/blog_migrate/e5a3acb87bf73f19587b93d5e8916304.png
IDL是HarmonyOS的接口描述语言,可以实现IPC跨历程间通信,接口的提供方是服务端,客户端绑定应用的服务来进行交互。
在IDL中界说服务端接口,代码如下。
登录后复制
**interface** com.wealchen.multipoint.IMultiPointIdl {

*//启动服务*
void serviceStart( int code);

*//发送消息*
void sendMsg( int code,int extras);

*//停止服务*
int serviceStop();
}
创建的IDL接口通过编译会在build > generated > source > Idl> 目次 debug 和release 下自动天生对应的接口类、桩类和代理类,如下图。
https://i-blog.csdnimg.cn/blog_migrate/58af04cb2dba626f7f5f0ef8cc7ee242.png
3. 实现后台接口服务

在HarmonyOS中,客户端绑定服务端后,获取到序列化的IRemoteObject对象,通过IRemoteObject对象实现客户端与服务端的通信,IRemoteObject在编译天生的桩类和代理类中已经完成了对象的创建和消息发送的实现,详细可检察上图中自动天生的代码。
创建一个Service后台服务MultiPointService,提供给客户端连接,并实现IDL中界说的接口,在接口实现中启动FA页面,接收客户端发送的消息,详细代码如下:
登录后复制
**public** **class** MultiPointService **extends** Ability {
*// DESCRIPTOR 保持与 MultiPointIdlStub 和 MultiPointIdlProxy中的一致*
**private** static final String DESCRIPTOR = "com.wealchen.multipoint.IMultiPointIdl";
**private** static final String TAG = MultiPointService.class.getName();

@Override
**protected** void onStart(Intent intent) {
    **super**.onStart(intent);
}

@Override
**protected** IRemoteObject onConnect(Intent intent) {
    **return** **new** MultiPointRemoteObject(DESCRIPTOR);
}


**private** **class** MultiPointRemoteObject **extends** MultiPointIdlStub {
    **public** MultiPointRemoteObject(String descriptor) {
      **super**(descriptor);
    }

    @Override
    **public** void serviceStart(int _code) **throws** RemoteException {
      *//启动WebViewAbility页面*
      Intent intent = **new** Intent();
      Operation operation = **new** Intent.OperationBuilder().withBundleName(getBundleName())
          .withAbilityName(WebViewAbility.class.getName()).build();
      intent.setOperation(operation);
      intent.setParam(Constants.INTENT_STAR_PARAM, _code);
      startAbility(intent);
      LogUtil.debug(TAG, "serviceStart : start WebViewAbility " + _code);
    }

    @Override
    **public** void sendMsg(int _code, int _extras) **throws** RemoteException {
      LogUtil.debug(TAG, "sendMsgcode: " + _code + " extras: " + _extras);
    }

    @Override
    **public** int serviceStop() **throws** RemoteException {
      **return** 0;
    }
}
}
4. 实现跨端启动应用

启动跨端应用需指定设备的DeviceId,通过设备管理器DeviceManager,可获取到当前同一网络下全部不是待机状态的设备,拿到DeviceId,DeviceName等设备信息,详细代码如下:
登录后复制
*//获取同一网络下的设备*
    List<DeviceInfo> deviceInfos = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
    **if** (deviceInfos == **null** && deviceInfos.size() == 0) {
      **return**;
    }
    **for** (int i = 0; i < deviceInfos.size(); i++) {
      String devId = deviceInfos.get(i).getDeviceId();
      String devName = deviceInfos.get(i).getDeviceName();
    }
通过DeviceId可以与指定设备的后台服务MultiPointService连接,实现长期交互,系统提供了connectAbility方法,实现跨设备PA连接与断开连接的本领,通过AbilitySlice的connectAbility接口跨设备连接到后台服务MultiPointService,发送消息到指定设备,设备在接收到消息之后可以实行相应的使命,从而实现跨设备应用使命的调度,连接服务实现代码如下:
登录后复制
*//启动远端设备的FA*
**private** void startAbilityFa(String devicesId, String event, int localExtras) {
    Intent intent = **new** Intent();
    Operation operation =
      **new** Intent.OperationBuilder()
            .withDeviceId(devicesId)
            .withBundleName(getBundleName())
            .withAbilityName(MultiPointService.class.getName())
            .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
            .build();
    intent.setOperation(operation);
    boolean connectFlag = connectAbility(intent, **new** IAbilityConnection() {
      @Override
      **public** void onAbilityConnectDone(ElementName elementName, IRemoteObject remoteObject, int extra) {
      LogUtil.debug(TAG, "onAbilityConnectDone extra:" + extra);
      multiPointIdl = MultiPointIdlStub.asInterface(remoteObject);
      **try** {
          **if** (multiPointIdl != **null**) {
            **switch** (event) {
            **case** "serviceStart":
                multiPointIdl.serviceStart(0);
                **break**;
            **case** "sendMsg":
                multiPointIdl.sendMsg(1001, localExtras);
                **break**;
            }
          }
      } **catch** (RemoteException e) {
          LogUtil.error(TAG, "connect successful,but have remote exception");
      }
      }

      @Override
      **public** void onAbilityDisconnectDone(ElementName elementName, int extra) {
      LogUtil.debug(TAG, "extra " + extra + " elementName " + elementName.getAbilityName());
      disconnectAbility(**this**);
      }
    });
    **if** (connectFlag) {
      Toast.toast(**this**, "transmit successful!", TOAST_DURATION);
    } **else** {
      Toast.toast(**this**, "transmit failed!Please try again later.", TOAST_DURATION);
    }
}
二、多端设备协同

多设备协同可以实现对跨端设备的控制,利用HarmonyOS的分布式数据服务,差别设备之间的数据可以实时更新并表现在界面上。
1. 分布式数据服务先容

分布式数据服务是HarmonyOS为应用程序提供差别设备间同步数据的本领,通过利用分布式数据接口,应用程序将数据生存到分布式数据库中,差别设备之间可以通太过布式数据服务互相访问,支持数据在相同帐号的多端设备之间相互同步。
HarmonyOS的分布式数据库是一种NoSQL范例数据库,其数据以键值对key-value的形式进行组织、索引和存储,分布式数据服务包罗五部分:
• 服务接口:提供专门的数据库创建、数据访问、数据订阅等接口给应用程序调用,接口支持KV数据模型,支持常用的数据范例,同时确保接口的兼容性、易用性和可发布性。
• 服务组件:负责服务内元数据管理、权限管理、加密管理、备份和规复管理以及多用户管理等、同时负责初始化底层分布式DB的存储组件、同步组件和通信适配层。
• 存储组件:负责数据的访问、数据的缩减、事件、快照、数据库加密,以及数据合并和冲突解决等特性。
• 同步组件:连结了存储组件与通信组件,其目的是保持在线设备间的数据库数据一致性,包罗将本地产生的未同步数据同步给其他设备,接收来自其他设备发送过来的数据,并合并到本地设备中。
• 通信适配层:负责调用底层公共通信层的接口完成通信管道的创建、连接,接收设备上下线消息,维护已连接和断开设备列表的元数据,同时将设备上下线信息发送给上层同步组件,同步组件维护连接的设备列表,同步数据时根据该列表,调用通信适配层的接口将数据封装并发送给连接的设备。
https://i-blog.csdnimg.cn/blog_migrate/8183f69676016b5ea1d5531900ddfabe.png
2. 创建分布式数据库

创建分布式数据库管理器,设置是否开启加密,自动同步功能,界说数据存储和查询方法,利用手动同步数据的方法实现数据的同步,详细代码代码如下:
登录后复制
**public** **class** MyKvStoreManager {
**private** static final String TAG = MyKvStoreManager.class.getName();
**private** static MyKvStoreManager instance = **null**;
**private** static SingleKvStore singleKvStore = **null**;
**private** static KvManager kvManager;

**private** MyKvStoreManager(Context context) {
    createKVManager(context);
}
*/**
** 创建分布式数据管理器*
** \*/*
**public** static void createKVManager(Context context) {
    KvManagerConfig config = **new** KvManagerConfig(context);
    kvManager = KvManagerFactory.getInstance().createKvManager(config);
    **try** {
      Options options = **new** Options();
      options.setCreateIfMissing(**true**)
          .setAutoSync(**false**)
          .setEncrypt(**false**)
          .setKvStoreType(KvStoreType.SINGLE_VERSION);
      String storeId = "remoteData";
      singleKvStore = kvManager.getKvStore(options, storeId);
    } **catch** (KvStoreException e) {
      LogUtil.error(TAG, "getKvStore:" + e.getKvStoreErrorCode());
    }
}

*/**
** 存储数据*
** \*/*
**public** static void putKvStore(String key, String value) {
    **if** (singleKvStore == **null**) {
      **return**;
    }
    **try** {
      singleKvStore.putString(key, value);
    } **catch** (KvStoreException e) {
      LogUtil.debug(TAG, "putString:" + e.getKvStoreErrorCode());
    }
}

*/**
** 查询数据*
** \*/*
**public** static String getKvStore(String key) {
    String valueString = "";
    **try** {
      valueString = singleKvStore.getString(key);
    } **catch** (KvStoreException e) {
      LogUtil.debug(TAG, "getString:" + e.getKvStoreErrorCode());
    }
    **return** valueString;
}

*/**
** 手动同步数据*
** \*/*
**public** static void syncData() {
    List<DeviceInfo> deviceInfoList = kvManager.getConnectedDevicesInfo(DeviceFilterStrategy.NO_FILTER);
    List<String> deviceIdList = **new** ArrayList<>();
    **for** (DeviceInfo deviceInfo : deviceInfoList) {
      deviceIdList.add(deviceInfo.getId());
      LogUtil.debug(TAG,"syncData getId: "+deviceInfo.getId());
    }
    **if** (deviceIdList.isEmpty()){
      **return**;
    }
    singleKvStore.sync(deviceIdList, SyncMode.PUSH_PULL);
}
   */**
** 订阅数据同步结果*
** \*/*
**public** static void dataChangeObserver(KvStoreObserver storeObserver) {
    singleKvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_REMOTE, storeObserver);
}
}
3. 订阅分布式数据变革

订阅分布式数据变革客户端应用须要实现KvStoreObserver接口,KvStoreObserver接口回调中返回数据更新的结果,根据返回结果应用实行相应的使命,或者更新界面,从而实现多端设备的同步,留意:回调方法中不答应更新UI组件等阻塞动作。
更新UI可以通过getMainTaskDispatcher().asyncDispatch()切换到UI主线程更新UI的,详细代码如下:
登录后复制
MyKvStoreManager.dataChangeObserver(**new** KvStoreObserver() {
@Override
**public** void onChange(ChangeNotification changeNotification) {
    LogUtil.debug(TAG, "dataChangeObserver");
    List<Entry> updateEntries = changeNotification.getUpdateEntries();
    **for** (int i = 0; i < updateEntries.size(); i++) {
      String key = updateEntries.get(i).getKey();
      String value = updateEntries.get(i).getValue().getString();
      LogUtil.debug(TAG, "dataChangeObserver key:" + key + " value:" + value);
      getMainTaskDispatcher().asyncDispatch(**new** Runnable() {
      @Override
      **public** void run() {
          **switch** (key) {
            **case** JS_ADD_NAME:
            *//添加名字*
            setJsAddName(value);
            **break**;
            **case** JS_DEL_NAME:
            *//删除名字*
            setJsDelName(value);
            **break**;
            **default**:
            updateListView(key, value);
            **break**;
          }
      }
      });

    }
}
});
三、WebView与JavaScript的交互

1. 在WebView调用H5页面的JavaScript方法

更新H5页面时,WebView须要调用JavaScript,通过WebView.executeJs()传入H5页面中对应的方法名称,实现应用调用页面内的JavaScript方法,实现的代码如下:
登录后复制
**private** void setJsAddName(String addName) {
    LogUtil.debug(TAG, "addName:" + addName);
    String add_js = String.format("javascript:addName('%s')", addName);
    webView.executeJs(add_js, **new** AsyncCallback<String>() {
      @Override
      **public** void onReceive(String msg) {
      *// 在此确认返回结果*
      LogUtil.debug(TAG, "executeJs onReceive:" + msg);
      }
    });
}

H5页面定义的方法:

**function** addName(name){
    **var** tpl = document.getElementById('name').innerText
    **var** str = name +'\n'
    document.getElementById('name').innerText = tpl.concat(str)
    **return** name
}
2. H5页面调用应用中的方法

WebView组件通过注入回调对象到页面内容,实现在H5页面中调用应用中的方法。
H5页面调用应用中的方法:
登录后复制
**function** callToApp() {
**if** (window.showDeviceList && window.showDeviceList.call) {
    **var** result = showDeviceList.call("showDeviceList");
}
}

应用中定义的方法:

webView.addJsCallback("showDeviceList", **new** JsCallback() {
@Override
**public** String onCallback(String s) {
    getMainTaskDispatcher().asyncDispatch(**new** Runnable() {
      @Override
      **public** void run() {
      **switch** (s) {
          **case** "showDeviceList":
            **if** (!bottomDialog.isShowing()) {
            bottomDialog.show();
            }
            **break**;
      }
      }
    });
    **return** method;
}
});
总结

应用实现了包罗原子化服务、使命流转、跨端启动应用、分布式数据服务、多端设备协同、WebView组件与JavaScript交互的功能,其中包罗了HarmonyOS系统才具有的功能。以上只是简单先容了HarmonyOS特有功能的实现,当然HarmonyOS的特性不止这些,更多的功能和实现还须要开发者去探索。
为了能让各人更好的学习鸿蒙 (OpenHarmony) 开发技术,这边特意整理了《鸿蒙 (OpenHarmony)开发学习手册》(共计890页),盼望对各人有所帮助:https://qr21.cn/FV7h05
《鸿蒙 (OpenHarmony)开发学习手册》

入门必看:https://qr21.cn/FV7h05


[*]应用开发导读(ArkTS)
[*]……
https://i-blog.csdnimg.cn/blog_migrate/9a7909caeda41690d631b78aadd3ce7f.png
HarmonyOS 概念:https://qr21.cn/FV7h05


[*]系统界说
[*]技术架构
[*]技术特性
[*]系统安全
https://i-blog.csdnimg.cn/blog_migrate/d53021bdb9a8df3920be94912e773b3f.png
如何快速入门?:https://qr21.cn/FV7h05


[*]基本概念
[*]构建第一个ArkTS应用
[*]构建第一个JS应用
[*]……
https://i-blog.csdnimg.cn/blog_migrate/9526f7e6208845c395a078b4df429b0b.png
开发基础知识:https://qr21.cn/FV7h05


[*]应用基础知识
[*]设置文件
[*]应用数据管理
[*]应用安全管理
[*]应用隐私掩护
[*]三方应用调用管控机制
[*]资源分类与访问
[*]学习ArkTS语言
[*]……
https://i-blog.csdnimg.cn/blog_migrate/fd6005244a94678d0490c8e285a000c9.png
基于ArkTS 开发:https://qr21.cn/FV7h05

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台使命(Background Task)管理
11.设备管理
12.设备利用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……
https://i-blog.csdnimg.cn/blog_migrate/ead1061f77783645a224720faea8bc4b.png

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: HarmonyOS - 实现多设备协同开发实战教程~