OpenHarmony分布式 启动远程设备的FA 原创 精华

NL_AIDC_XJS
发布于 2022-4-22 12:44
浏览
7收藏

目标

基于OpenHarmony提供的分布式能力,实现在同一个网络下拉起远程设备的Ability,本节主要分享分布式中最基础的能力,分布式拉起的实现

效果

OpenHarmony分布式 启动远程设备的FA-鸿蒙开发者社区

环境

前置条件

1、在Hi3616开发板上烧写OpenHarmony 3.1 release系统
2、准备两台烧录相同的版本系统的Hi3516DV300开发板A,B
3、两个开发板A,B配置在同一个WiFi网络之下。打开设置–>WLAN–>点击右侧WiFi开关–>点击目标WiFi并输入密钥。

分布式实现

以下内容是本地学习和整理的材料,如果有不对的地方请留言指正,谢谢。

流程图

OpenHarmony分布式 启动远程设备的FA-鸿蒙开发者社区

流程说明

1、用户授权,分布式启动远程FA需要申请敏感权限:ohos.permission.DISTRIBUTED_DATASYNC,用户授权成功后才可以使用;
2、通过deviceManager.createDeviceManager()创建设备管理对象实例DeviceManager;
3、同步获取所有可信设备列表:deviceManager.getTrustedDeviceListSync();
4、启动发现设备:deviceManager.startDeviceDiscovery();
5、注册监听器:deviceManager.on()

  • deviceStateChange:设备状态发生改变,比如上班上线、下线、状态同步完成
  • deviceFound:发现设备
  • discoverFail:发现设备失败
  • serviceDie:服务停止

6、设备认证:deviceManager.authenticateDevice(),两台设备互联需要先建立认证,认证通过两台设备即为可信设备,可以进行一些分布式操作;
7、启动远端FA,featureAbility.startAbility(),其中featureAbility是@ohos.ability.featureAbility。

核心代码分析

分布式权限

权限声明

说明:需要申请的权限可以在config.json的module模块中进行声明。

module{
"reqPermissions": [
      {
        "name": "ohos.permission.DISTRIBUTED_DATASYNC"
      }
    ]
}
权限申请

说明:敏感权限需要用户授权后才可以访问,本案例应用启动就需要申请,所以在index.js的onInit()方法中申请。

接口说明

AbilityContext.requestPermissionsFromUser(permissions: Array<string>, requestCallback: AsyncCallback<PermissionRequestResult>) : void;

参数说明
参数名 类型 必填 说明
permissions Array<string> 权限列表。
callback AsyncCallback<PermissionRequestResult> 回调函数,返回接口调用是否成功的结果。
参数:PermissionRequestResult

说明:权限请求结果。
备注:本模块首批接口从API 9开始支持。后续版本的新增接口,采用上角标单独标记接口的起始版本。

名称 参数类型 可读 可写 说明
permissions Array<string> 用户传入的权限
authResults Array<number> 相应请求权限的结果。0表示授权成功,-1表示失败。
实现
    grantPermission() {
        let context = featureAbility.getContext();
        context.requestPermissionsFromUser(REQUEST_PERMISSION_LIST, REQUEST_PERMISSION_CODE, function(result){
            console.log('request Permissions From User Result:' + JSON.stringify(result));
            // todo 判断是否授权
          }
   }

获取DeviceManager实例

说明:需要操作设备必须获取设备实例才可以=,ohos.distributedHardware.deviceManager中提供了创建的方法:createDeviceManager(bundleName: string, callback: AsyncCallback<DeviceManager>): void
备注:DeviceManager API

参数说明
参数名 类型 必填 说明
bundleName string 指示应用程序的包名。
callback AsyncCallback<DeviceManager> DeviceManager实例创建时调用的回调,返回设备管理器对象实例。
实现

registerDeviceListCallback(callback) {
        if (typeof (this.#deviceManager) === 'undefined') {
            // 获取设备管理对象
            let self = this;
            deviceManager.createDeviceManager('com.nlas.myapplication',
                (error, value) => {
                    if (error) {
                        console.error('createDeviceManager failed.');
                        return;
                    }
                    self.#deviceManager = value;// 设备管理器对象实例
                    // 执行操作...
                })
        } else {
            // 设备管理器对象已经存在,直接可以执行操作...
        }
    }

获取可信设备

说明:同步获取所有可信设备列表
deviceManager.getTrustedDeviceListSync(): Array<DeviceInfo>

参数说明
名称 说明
Array<DeviceInfo> 返回可信设备列表。
实现
// 同步获取所有可信设备列表
var list = this.#deviceManager.getTrustedDeviceListSync();
console.info('[RemoteDeviceModel] deviceList=' + JSON.stringify(list));

开始发现设备

接口:deviceManager.startDeviceDiscovery(subscribeInfo: SubscribeInfo):void

接口说明
参数 类型 必填 说明
subscribeInfo SubscribeInfo 设备发现的服务订阅信息
参数:SubscribeInfo
名称 参数 说明
subscribeId number 服务订阅ID,取值范围为[0,65535],对于每个发现进程应该是唯一
mode DiscoverMode 服务订阅的发现方式, DISCOVER_MODE_PASSIVE = 0x55-》被动; DISCOVER_MODE_ACTIVE = 0xAA-》主动
medium ExchangeMedium 设备发现介质,AUTO-》自动选择;BLE-》蓝牙; COAP-》Wi-Fi;USE-》USB
freq ExchangeFreq 服务订购频率 ,LOW=0; MID=1; HIGH=2;SUPER_HIGH=3
isSameAccount boolean 只能找到帐号相同的设备。
isWakeRemote boolean 是否发现休眠设备
capability SubscribeCap 订阅功能,设备发现能力,SUBSCRIBE_CAPABILITY_DDMP = 0;SUBSCRIBE_CAPABILITY_OSD = 1
实现

 // 随机数
SUBSCRIBE_ID = Math.floor(65536 * Math.random());
var info = {
            subscribeId: SUBSCRIBE_ID,// 服务订阅ID【0~65535】,对每个发现进程来说应该是唯一的
            mode: 0xAA,//主动模式
            medium: 2,//订阅媒介 2-wifi
            freq: 2,// 订阅频率 高
            isSameAccount: false,// 只能找到帐号相同的设备
            isWakeRemote: true,// 发现休眠设备
            capability: 0
        };
console.info('[RemoteDeviceModel] startDeviceDiscovery ' + SUBSCRIBE_ID);
deviceManager.startDeviceDiscovery(info);// 发现设备

注册监听器

注册设备状态回调
关键字:deviceStateChange
接口:deviceManager.on(type: ‘deviceStateChange’, callback: Callback<{ action: DeviceStateChangeAction, device: DeviceInfo }>): void

参数说明
参数名 参数类型 必填 说明
type string 注册设备状态回调,固定为deviceStateChange。
callback Callback<{ action: DeviceStateChangeAction, device: DeviceInfo }> 指示要注册的设备状态回调,返回设备状态和设备信息。
参数:DeviceStateChangeAction

说明:表示设备状态变化的枚举

名称 默认值 说明
ONLINE 0 设备上线。
READY 1 设备就绪,设备信息同步已完成。
OFFLINE 2 设备下线。
CHANGE 3 设备信息更改。
参数:DeviceInfo

说明:设备信息

参数名 类型 必填 描述
deviceId number 设备的唯一标识
deviceName string 设备名称
deviceType number 设备类型
networkId string 设备的网络ID,API8
参数:DeviceType

说明:表示设备类型的枚举类。

名称 默认值 说明
SPEAKER 0x0A 智能音箱
PHONE 0x0E 手机
TABLET 0x11 平板
WEARABLE 0x6D 智能穿戴
TV 0x9C 智慧屏
CAR 0x83
UNKNOWN_TYPE 0 未知设备
实现

        deviceManager.on(DEVICE_STATE_CHANGE, (data) => {
            console.info('[RemoteDeviceModel] deviceStateChange data=' + JSON.stringify(data));
            if (data == null) {
                return;
            }
            switch (data.action) {
                case 0:
                {
                    self.deviceList[self.deviceList.length] = data.device;
                    console.info('[RemoteDeviceModel] online, updated device list=' + JSON.stringify(self.deviceList));
                    self.callback();
                    if (self.authCallback != null) {
                        self.authCallback();
                        self.authCallback = null;
                    }
                    break;
                }
                case 1:
                {
                      if (self.deviceList.length > 0) {
                        for (var i = 0; i < self.deviceList.length; i++) {
                            if (self.deviceList[i].deviceId === data.device.deviceId) {
                                self.deviceList[i] = data.device;
                                break;
                            }
                        }
                    }
                    console.info('[RemoteDeviceModel] change, updated device list=' + JSON.stringify(self.deviceList));
                    self.callback();
                    break;
                }
                case 2:
                {
                    const count = self.deviceList.length;
                    if (count > 0) {
                        var list = [];
                        for (var i = 0; i < count; i++) {
                            if (self.deviceList[i].deviceId != data.device.deviceId) {
                                list[i] = data.device;
                            }
                        }
                        self.deviceList = list;
                    }
                    console.info('[RemoteDeviceModel] offline, updated device list=' + JSON.stringify(data.device));
                    self.callback();
                    break;
                }
                default:
                    break;
            }
        });

反注册设备状态回调

说明:在不需要监听时需要取消注册的监听器,减少资源浪费。
接口:deviceManager.off(type: ‘deviceStateChange’, callback?: Callback<{ action:
DeviceStateChangeAction, device: DeviceInfo }>): void;

实现
deviceManager.off('deviceStateChange');

注册发现设备回调

关键字:deviceFound
接口:deviceManager.on(type: ‘deviceFound’, callback: Callback<{ subscribeId: number, device:
DeviceInfo }>): void;

参数说明
参数名 参数类型 必填 说明
type string 注册发现设备回调,固定为deviceFound。
callback Callback<{ subscribeId: number, device: DeviceInfo }> 发现要注册的设备回调。
实现

deviceManager.on(DEVICE_FOUND, (data) => {
            console.info('[RemoteDeviceModel] deviceFound data=' + JSON.stringify(data));
            if (data == null) {
                return;
            }
            const count = self.discoverList.length;
            for (var i = 0; i < count; i++) {
                if (self.discoverList[i].deviceId === data.device.deviceId) {
                    // 设备已经在列表中
                    return;
                }
            }
            // 新增发现的设备
            self.discoverList[count] = data.device;
            self.callback();
        });

反注册发现设备回调

说明:在不需要监听时需要取消注册的监听器,减少资源浪费。
接口:deviceManager.off(type: ‘deviceFound’, callback?: Callback<{ subscribeId: number, device:
DeviceInfo }>): void;

实现
deviceManager.off('deviceFound');

设备认证

说明:认证指定的设备
接口:deviceManager.authenticateDevice(deviceInfo: DeviceInfo, authParam: AuthParam, callback:
AsyncCallback<{deviceId: string, pinTone ?: number}>): void;

参数说明
参数名 类型 说明
deviceInfo DeviceInfo 设备信息,包括:deviceId、deviceName、deviceType
authParam AuthParam 认证参数
callback AsyncCallback 认证回调
参数:AuthParam
参数名 类型 说明
authType number 认证类型,1-》PIN码认证
extraInfo object 扩展信息
实现

authDevice(deviceId, callback) {
        console.info('[RemoteDeviceModel] authDevice ' + deviceId);
        for (var i = 0; i < this.discoverList.length; i++) {
            if (this.discoverList[i].deviceId === deviceId) {
                // 在线设备中存在此设备,创建扩展信息
                let extraInfo = {
                    "targetPkgName": 'com.nlas.myapplication',
                    "appName": 'demo',
                    "appDescription": 'demo application',
                    "business": '0'
                };
                let authParam = {
                    "authType": 1,// 认证类型,1为pin码。
                    "appIcon": '',
                    "appThumbnail": '',
                    "extraInfo": extraInfo // 扩展信息:key-value
                };
                console.info('[RemoteDeviceModel] authenticateDevice ' + JSON.stringify(this.discoverList[i]));
                this.authDeviceInfo = this.discoverList[i];
                let self = this;
                this.#deviceManager.authenticateDevice(this.authDeviceInfo, authParam, (err, data)=>{
                    if (err) {
                        console.info('[RemoteDeviceModel] authenticateDevice failed, err=' + JSON.stringify(err));
                        self.authCallback = null;
                    } else {
                        console.info('[RemoteDeviceModel] authenticateDevice succeed, data=' + JSON.stringify(data));
                        self.authCallback = callback;
                    }
                })
            }
        }
    }

启动远程设备上的FA

说明:通过@ohos.ability.featureAbility 提供的 startAbility() 接口启动远程的FA.
接口:featureAbility.startAbility(parameter: StartAbilityParameter): Promise<number>

参数说明

启动新的ability(Promise形式)。

参数名 类型 必填 描述
parameter StartAbilityParameter 表示被启动的Ability
参数:StartAbilityParameter
参数名 读写 类型 必填 描述
want 只读 Want 表示需要包含有关目标启动能力的信息。
abilityStartSetting 只读 {[key: string]: any} 表示能力的特殊属性,当开发者启动能力时,该属性可以作为调用中的输入参数传递。
参数:Want
名称 读写属性 类型 必填 描述
deviceId 只读 string 表示运行指定Ability的设备ID。
bundleName 只读 string 表示包描述。
abilityName 只读 string 表示待启动的Ability名称。如果在Want中该字段同时指定了package和AbilityName,则Want可以直接匹配到指定的Ability。
uri 只读 string 表示Uri描述。如果在Want中指定了Uri,则Want将匹配指定的Uri信息,包括scheme, schemeSpecificPart, authority和path信息。
type 只读 string 表示MIME type类型描述,比如:“text/plain” 、 "image/*"等。
flags 只读 number 表示处理Want的方式。默认传数字,具体参考:[flags说明]
action 只读 string 表示action选项描述。
parameters 只读 {[key: string]: any} 表示WantParams描述。
entities 只读 Array<string> 表示entities相关描述。
extensionAbilityName<sup>9+<sup> 只读 string Want中扩展能力名称的描述。
extensionAbilityType<sup>9+<sup> 只读 number Want中扩展能力类型的描述。
参数:flags说明
名称 参数 描述
FLAG_AUTH_READ_URI_PERMISSION 0x00000001 指示对URI执行读取操作的授权。
FLAG_AUTH_WRITE_URI_PERMISSION 0x00000002 指示对URI执行写入操作的授权。
FLAG_ABILITY_FORWARD_RESULT 0x00000004 将结果返回给元能力。
FLAG_ABILITY_CONTINUATION 0x00000008 确定是否可以将本地设备上的功能迁移到远程设备。
FLAG_NOT_OHOS_COMPONENT 0x00000010 指定组件是否属于OHOS。
FLAG_ABILITY_FORM_ENABLED 0x00000020 指定是否启动某个能力。
FLAG_AUTH_PERSISTABLE_URI_PERMISSION 0x00000040 指示URI上可能持久化的授权。
FLAG_AUTH_PREFIX_URI_PERMISSION 0x00000080 按照前缀匹配的方式验证URI权限。
FLAG_ABILITYSLICE_MULTI_DEVICE 0x00000100 支持分布式调度系统中的多设备启动。
FLAG_START_FOREGROUND_ABILITY 0x00000200 指示无论主机应用程序是否已启动,都将启动使用服务模板的功能。
FLAG_ABILITY_CONTINUATION_REVERSIBLE 0x00000400 表示迁移是可拉回的。
FLAG_INSTALL_ON_DEMAND 0x00000800 如果未安装指定的功能,请安装该功能。
FLAG_INSTALL_WITH_BACKGROUND_MODE 0x80000000 如果未安装,使用后台模式安装该功能。
FLAG_ABILITY_CLEAR_MISSION 0x00008000 指示清除其他任务的操作。可以为传递给 ohos.app.ContextstartAbility方法的Want设置此标志,并且必须与flag_ABILITY_NEW_MISSION一起使用。
FLAG_ABILITY_NEW_MISSION 0x10000000 指示在历史任务堆栈上创建任务的操作。
FLAG_ABILITY_MISSION_TOP 0x20000000 指示如果启动能力的现有实例已位于任务堆栈的顶部,则将重用该实例。否则,将创建一个新的能力实例。
实现

 startAbilityContinuation(deviceId, deviceName) {
        this.$element('continueAbilityDialog').close();
        console.info('featureAbility.startAbility deviceId=' + deviceId
        + ' deviceName=' + deviceName);
        const wantValue = {
            bundleName: 'com.nlas.myapplication',
            abilityName: 'com.example.entry.MainAbility',
            deviceId: deviceId
        };
        featureAbility.startAbility({
            want: wantValue
        }).then((data) => {
            console.info('featureAbility.startAbility finished, ' + JSON.stringify(data));
        });
        console.info('featureAbility.startAbility want=' + JSON.stringify(wantValue));
    },

坑点:这里有很多同学调用startAbility后并没有拉起远端的FA,主要的原因是在wantValue.abilityName参数的配置出现错误

正确的打开方式:
abilityName=package+mainAbility,package和mainAbility都在config.json文件的module模块中,比如你的

  • “package”: “com.example.entry”;
  • “mainAbility”: “.MainAbility”,
    那么wantValue.abilityName: ‘com.example.entry.MainAbility’。

到此为止,OpenHarmony分布式调度启动远程FA的实现,如果有地方介绍有错误请指正。

完整代码

请在文章末尾下载。

感谢

如果您能看到最后,还希望您能动动手指点个赞,一个人能走多远关键在于与谁同行,我用跨越山海的一路相伴,希望得到您的点赞。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
RemoteStartFACode.zip 39.14K 88次下载
已于2022-5-7 09:21:55修改
10
收藏 7
回复
举报
8条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

非常详细的分布式教程,我先学起来。

回复
2022-4-22 13:54:08
Neverland09
Neverland09

有小型系统拉起标准系统FA的demo吗?

回复
2022-6-22 14:12:41
NL_AIDC_XJS
NL_AIDC_XJS 回复了 Neverland09
有小型系统拉起标准系统FA的demo吗?

在release3.1版本上暂时不支持,因为小型系统与标准系统设备发现的机制还不完善。

回复
2022-6-23 10:49:44
wx631807b2aa6b8
wx631807b2aa6b8

请问是不是要先用分布式音乐播放器进行组网认证之后,才能运行本文的启动远端FA吗?

您可以把代码放在gitee上,点你点star

回复
2022-10-20 12:05:19
NL_AIDC_XJS
NL_AIDC_XJS 回复了 wx631807b2aa6b8
请问是不是要先用分布式音乐播放器进行组网认证之后,才能运行本文的启动远端FA吗?您可以把代码放在gitee上,点你点star

是的,分布式组网后才能拉起远程应用

分布式组网可以通过系统自带的音乐播放器进行,也可以自己在应用中实现一次,分布式消息通道创建后就可以直接复用了。

回复
2022-10-20 15:38:28
物联风景
物联风景

不错不错,很详细

回复
2022-10-21 09:10:18
wx631807b2aa6b8
wx631807b2aa6b8 回复了 NL_AIDC_XJS
是的,分布式组网后才能拉起远程应用分布式组网可以通过系统自带的音乐播放器进行,也可以自己在应用中实现一次,分布式消息通道创建后就可以直接复用了。

是的,但是在自己的应用中实现的话,需要导入支持PIN码认证的系统弹框FA,这点麻烦些

回复
2022-10-22 00:36:36
NL_AIDC_XJS
NL_AIDC_XJS 回复了 wx631807b2aa6b8
是的,但是在自己的应用中实现的话,需要导入支持PIN码认证的系统弹框FA,这点麻烦些

标准系统中已经存在了PIN码弹窗,不需要额外导入

回复
2022-10-24 11:36:01
回复
    相关推荐