作者:卞绍雷

前言

鸿蒙系统(HarmonyOS)支持在本地程序里调起远程任务,这个功能是更底层的分布式任务调度子系统支撑的,并且已经贡献到开源鸿蒙(OpenHarmony)代码里。为了更全面地理解与掌握分布式任务调度子系统,我先从分布式远程启动这个简单的功能开始,利用开源鸿蒙开放的源代码,进行深入学习。

以下行文如无特别说明,所述说的鸿蒙系统均指开源鸿蒙系统(OpenHarmony 3.0 LTS版本)。

image20211105182907972.png ::: hljs-center

OpenHarmony 架构图

:::

概述

先从开源鸿蒙文档入手:

分布式任务调度模块,通过主从设备服务代理机制,在OpenHarmony操作系统上建立起分布式服务平台,支持主设备(搭载OpenHarmony的智慧屏设备)启动从设备(IP Camera、运动手表等小内存OpenHarmony设备)FA的能力。

以智慧屏节目开播提醒为例,智慧屏上在喜欢的节目菜单中,点击“开播后提醒我”按钮,等节目开播后,智慧屏会拉起运动手表上的节目开播提醒FA。通过该FA用户可以快速知道喜欢的节目已经开始,达到协同互助的作用。

FA : Feature Ability代表有界面的Ability,用于与用户进行交互。 远程启动:即跨设备启动FA,与本地启动FA相对应。

开源鸿蒙系统里的应用程序以Ability为单位,分为FA和PA, 可以简单理解为FA就是有界面的应用程序。

分布式服务平台

分布式任务调度的前提,是设备必须建立分布式服务平台,并且注册自身能力。

开源鸿蒙支持3种体量的设备:轻量、小型、标准。

在标准设备里,开源鸿蒙默认已经开启了分布式服务平台,开发者一般无需做额外工作,即可使用分布式任务调度功能。

而轻量和小型设备则需要自行在启动代码中实现分布式服务平台功能调用,具体可以参考【分布式软总线子系统】。相关代码仓及调用API可以参考:【分布式软总线】【分布式软总线lite】

同一个局域网

在上述代码仓的说明中,一再强调了:

需要保证发现端设备与被发现端设备在同一个局域网内

是因为目前开源鸿蒙系统使用了coap协议,并且暂时只支持coap协议。从源代码中可以看到,以后应该会扩展到BLE、USB等方式。

/**
 * @brief Enumerates media, such as Bluetooth, Wi-Fi and USB, used for publishing services.
 *
 * Currently, the media can only be set to coap.
 *
 */
typedef enum {
    /** Automatic medium selection */
    AUTO = 0,
    /** Bluetooth */
    BLE = 1,
    /** Wi-Fi */
    COAP = 2,
    /** USB */
    USB = 3,
} ExchangeMedium;

开源鸿蒙的coap协议默认使用的端口是5684,在局域网通过udp广播方式发布。如果调试过程中找不到设备,可以通过这个端口抓包分析。

#define COAP_DEFAULT_PORT 5684

分布式任务调度流程

手头正好有两块支持开源鸿蒙标准系统的开发板Hi3516D,因此以标准系统的分布式任务调度为例,说明开源鸿蒙系统的分布式任务调度流程。轻量系统和小型系统除了开发语言不同,基本步骤是一致的。

开源鸿蒙系统开发FA目前只支持js/eTS语言。

这个demo参考了分布式计算器,为说明方便,做了大量简化处理,并且此处忽略了错误处理。

具体api参考代码仓:DeviceManager组件

步骤1:创建设备管理器

import deviceManager from '@ohos.distributedHardware.deviceManager';

let self = this;
deviceManager.createDeviceManager("com.example.myapplication", (err, val)=>{self.deviceManager_ = val;});

使用DeviceManager相关接口之前,需要通过createDeviceManager接口创建DeviceManager实例; 不使用DeviceManager接口的时候需要释放对应的DeviceManager实例。

原型 描述
createDeviceManager(bundleName: string, callback: AsyncCallback<DeviceManager>): void; 以异步方法获取DeviceManager实例
release(): void; 释放DeviceManager实例

步骤2:获取可信设备列表

var array = this.deviceManager_.getTrustedDeviceListSync();
原型 描述
getTrustedDeviceListSync(): Array<DeviceInfo>; 获取信任设备列表

步骤3:注册周边设备动态监控回调函数

this.deviceManager_.on('deviceFound', (data) => {
    let extraInfo = {
        "targetPkgName": 'com.example.myapplication',
        "appName": '分布式例子',
        "appDescription": '一个简单的分布式例子',
        "business": '0'
    };
    let authParam = {
        "authType": 1,
        "appIcon": '',
        "appThumbnail": '',
        "extraInfo": extraInfo
    };
    self.deviceManager_.authenticateDevice(data.device, authParam, (err) => { ... });
});
this.deviceManager_.on('deviceStateChange', (data) => { ... });

原型 描述
on(type: 'deviceStateChange', callback: Callback<{ action: DeviceStateChangeAction, device: DeviceInfo }>): void; 设备状态变更回调
off(type: 'deviceStateChange', callback?: Callback<{ action: DeviceStateChangeAction, device: DeviceInfo }>): void; 取消设备状态变更回调
on(type: 'deviceFound', callback: Callback<{ subscribeId: number, device: DeviceInfo }>): void; 发现设备列表回调
off(type: 'deviceFound', callback?: Callback<{ subscribeId: number, device: DeviceInfo }>): void; 取消发现设备列表回调
on(type: 'discoverFail', callback: Callback<{ subscribeId: number, reason: number }>): void; 发现设备失败回调
off(type: 'discoverFail', callback?: Callback<{ subscribeId: number, reason: number }>): void; 取消发现设备失败回调
on(type: 'serviceDie', callback: () => void): void; 服务错误回调
off(type: 'serviceDie', callback?: () => void): void; 取消服务错误回调

步骤4:发现周边新设备,并认证

SUBSCRIBE_ID = Math.floor(65536 * Math.random());
var info = {
    subscribeId: SUBSCRIBE_ID,
    mode: 0xAA,
    medium: 2,
    freq: 2,
    isSameAccount: false,
    isWakeRemote: true,
    capability: 0
};
this.deviceManager_.startDeviceDiscover(info);
原型 描述
startDeviceDiscovery(subscribeInfo: SubscribeInfo): void; 开始设备发现
stopDeviceDiscovery(subscribeId: number): void; 停止发现设备
authenticateDevice(deviceInfo: DeviceInfo, authparam: AuthParam, callback: AsyncCallback<{deviceId: string, pinTone ?: number}>): void; 设备认证接口
verifyAuthInfo(authInfo: AuthInfo, callback: AsyncCallback<{deviceId: string, level: number}>): void; 设备认证信息校验

新设备需要认证才能互联使用。开源鸿蒙系统没有用户注册机制,因此认证需要另外开发框架支持,开源鸿蒙在标准系统提供了一个简单的HAP程序支持弹窗PIN码认证机制,可以简单使用。

当前版本只支持PIN码认证,需要提供PIN码认证的授权提示界面、PIN码显示界面、PIN码输入界面; 当前,由于系统通过native层直接进行弹窗的能力尚不具备,这里使用一个临时的FA来进行对应界面的弹窗。 该FA为:DeviceManager_UI.hap,作为系统应用进行预置。

具体行为是:

  1. 远程设备在屏幕显示一个巨大的6位数字PIN码
  2. 控制设备弹出一个PIN码输入窗口
  3. 用户在控制设备输入远程设备所显示的PIN码,通过验证即可继续远程控制
  4. PIN码输入成功后,该设备成为可信设备存储于系统,下次再次连接时不需要再次验证

步骤5:远程调用FA

以上步骤1~步骤4是标准的周围设备管理步骤,因此可以封装成函数库,方便后续使用。

在获取到可信设备数组后,可以在适当的弹窗或者选择界面,让用户选择其中一个进行连接。

    findDevices: function(){
        let self = this;
        this.remoteDevices.registerDeviceListCallback(() => {
            var list = new Array();
            var devs = self.remoteDevices.deviceList;
            console.info('myapplication: on remote device updated, count=' + devs.length);
            for (var i = 0; i < devs.length; i++) {
                console.info('myapplication: device ' + i + '/' + devs.length +
                ' deviceId=' + devs[i].deviceId + ' deviceName=' + devs[i].deviceName
                + ' deviceType=' + devs[i].deviceType);
                list[i + 1] = {
                    name: devs[i].deviceName,
                    did:  devs[i].deviceId
                };
            }
            self.devList = list;
        });
    },

启动远程FA的程序:

import featureAbility from '@ohos.ability.featureability';

......

featureAbility.startAbility({
    want:{
        bundleName: 'com.example.myapplication',
        abilityName: 'com.example.myapplication.MainAbility',
        deviceId: this.devList[idx].did,
        parameters: {
            isFA: 'FA'
        }
    }
}).then((data)=>{
    console.log("myapplication: start ability finished:" + JSON.stringify(data));
});

远程设备收到请求后,就会以对应的参数启动相应的FA,并且在启动时可以获取到参数:

onReady() {
    featureAbility.getWant((error, want) => {
        console.info('myapplication: featureAbility.getWant =' + JSON.stringify(want.parameters));
        // 这里isFA就是上面远程请求的参数
        if (want.parameters.isFA && want.parameters.isFA === 'FA') {
            this.initKVManager(()=>{
                console.log('myapplication: kvmanager started.')
            });
        }
        else{
            this.findDevices();
        }
    });

},

具体api可以参考华为鸿蒙开发文档,请注意里面部分内容可能与开源鸿蒙系统有区别,请自行确定。

编译运行

以上步骤,均已在DevEco Studio 3.0.0.600 x64中编写成功,并且在两台Hi3516D设备间成功运行,附代码(分布式远程启动.zip)。

想在开源鸿蒙系统上安装HAP程序,必须要先进行签名,并且在DevEco Studio中进行相关设置。具体步骤参考开源鸿蒙文档

上传安装HAP程序,需要使用开发工具hdc,具体参考文档

小结

开源鸿蒙系统的分布式任务调度基本功能已经初步完善了,使用文档比较分散,需要到各个子系统去查阅,略有不便。

下一步,学习一下分布式软总线、分布式数据,看看开源鸿蒙系统是如何封装应用之间数据交互能力的。

另外看源代码,开源鸿蒙系统已经有分布式应用流转(迁移)运行功能,有时间可以学习一下。

更多原创内容请关注:开鸿 HarmonyOS 学院

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

【本文正在参与51CTO HarmonyOS技术社区创作者激励-星光计划1.0】

想了解更多关于鸿蒙的内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com/#bkwz

::: hljs-center

21_9.jpg

:::