#夏日挑战赛#【FFH】分布式任务调度流转实现(OpenHarmony JSUI) 原创 精华

发布于 2022-6-15 16:45
浏览
5收藏

本文正在参加星光计划3.0–夏日挑战赛
@[TOC](#夏日挑战赛#【FFH】分布式任务调度之流转(OpenHarmony JS UI版))

Demo效果展示

#夏日挑战赛#【FFH】分布式任务调度流转实现(OpenHarmony JSUI)-开源基础软件社区
​ 我们这次实操的最终效果就像上面一样,就是在设备A的屏幕上点击“流转FA”按钮,就终止设备A的FA并且拉起设备B的FA。

​ 我们先分解一下这个demo的开发步骤,首先就是点击按钮需要识别到组网内的设备,然后进行设备选择,最后就实现流转。内容很简单,关键的就是在设备识别,以及流转API调用。下面我们来看看实际开发。

具体实现

​ 具体的开发可以分为一下几个关键步骤:

  • 获取分布式数据同步权限
  • 设备管理类封装
  • 设备识别函数实现以及流转函数实现
  • 编写界面样式以及事件绑定

1.获取分布式数据同步权限

​ 如果应用程序需要某些权限,例如存储、位置信息和日志访问权限,则应用程序必须向终端用户请求对应权限。确定需要的权限后,在config.json文件中添加权限。本例中,需要获取分布式数据同步权限,因此,需要在config.json中请求添加分布式数据同步权限,修改config.json文件如下:

"reqPermissions": [  
      {  
        "name": "ohos.permission.DISTRIBUTED_DATASYNC"  
      }  
    ]

代码段具体添加的位置如下图:

#夏日挑战赛#【FFH】分布式任务调度流转实现(OpenHarmony JSUI)-开源基础软件社区

同时,在页面代码段中申请用户权限:

编写一个申请权限函数,然后再初始化页面时调用,具体代码如下所示。

    onInit() {
        this.grantPermission()      
    },
    //获取用户权限
    grantPermission() {
        console.info(`FSRxxxxx grantPermission`)
        let context = featureAbility.getContext()
        context.requestPermissionsFromUser(['ohos.permission.DISTRIBUTED_DATASYNC'], 666, function (result) {
            console.info(`FSRxxxxx grantPermission,requestPermissionsFromUser`)
        })
    },

2.设备管理类封装

​ 这个类库是通用的,凡涉及到需要识别组网内设备,并且进行设备管理的,都可以调用此类库,并且这个类在官方Gitee上的案例都有,这边我们就直接使用Gitee上的设备管理类,创建远程设备模型来进行操作。

#夏日挑战赛#【FFH】分布式任务调度流转实现(OpenHarmony JSUI)-开源基础软件社区

我们先像上图一样创建一个model文件夹,把我们要调用的类放在这里面就行,代码如下:

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

var SUBSCRIBE_ID = 100;

export default class RemoteDeviceModel {
    deviceList = [];
    discoverList = [];
    callback;
    authCallback = null;
    #deviceManager;

    constructor() {
    }

    registerDeviceListCallback(callback) {
        if (typeof (this.#deviceManager) === 'undefined') {
            console.log('CookBook[RemoteDeviceModel] deviceManager.createDeviceManager begin');
            let self = this;
            deviceManager.createDeviceManager('com.ohos.distributedRemoteStartFA', (error, value) => {
                if (error) {
                    console.error('createDeviceManager failed.');
                    return;
                }
                self.#deviceManager = value;
                self.registerDeviceListCallback_(callback);
                console.log('CookBook[RemoteDeviceModel] createDeviceManager callback returned, error=' + error + ' value=' + value);
            });
            console.log('CookBook[RemoteDeviceModel] deviceManager.createDeviceManager end');
        } else {
            this.registerDeviceListCallback_(callback);
        }
    }

    registerDeviceListCallback_(callback) {
        console.info('CookBook[RemoteDeviceModel] registerDeviceListCallback');
        this.callback = callback;
        if (this.#deviceManager == undefined) {
            console.error('CookBook[RemoteDeviceModel] deviceManager has not initialized');
            this.callback();
            return;
        }

        console.info('CookBook[RemoteDeviceModel] getTrustedDeviceListSync begin');
        var list = this.#deviceManager.getTrustedDeviceListSync();
        console.info('CookBook[RemoteDeviceModel] getTrustedDeviceListSync end, deviceList=' + JSON.stringify(list));
        if (typeof (list) != 'undefined' && typeof (list.length) != 'undefined') {
            this.deviceList = list;
        }
        this.callback();
        console.info('CookBook[RemoteDeviceModel] callback finished');

        let self = this;
        this.#deviceManager.on('deviceStateChange', (data) => {
            console.info('CookBook[RemoteDeviceModel] deviceStateChange data=' + JSON.stringify(data));
            switch (data.action) {
                case 0:
                self.deviceList[self.deviceList.length] = data.device;
                console.info('CookBook[RemoteDeviceModel] online, updated device list=' + JSON.stringify(self.deviceList));
                self.callback();
                if (self.authCallback != null) {
                    self.authCallback();
                    self.authCallback = null;
                }
                break;
                case 2:
                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('CookBook[RemoteDeviceModel] change, updated device list=' + JSON.stringify(self.deviceList));
                self.callback();
                break;
                case 1:
                if (self.deviceList.length > 0) {
                    var list = [];
                    for (var i = 0; i < self.deviceList.length; i++) {
                        if (self.deviceList[i].deviceId != data.device.deviceId) {
                            list[i] = data.device;
                        }
                    }
                    self.deviceList = list;
                }
                console.info('CookBook[RemoteDeviceModel] offline, updated device list=' + JSON.stringify(data.device));
                self.callback();
                break;
                default:
                    break;
            }
        });
        this.#deviceManager.on('deviceFound', (data) => {
            console.info('CookBook[RemoteDeviceModel] deviceFound data=' + JSON.stringify(data));
            console.info('CookBook[RemoteDeviceModel] deviceFound self.deviceList=' + self.deviceList);
            console.info('CookBook[RemoteDeviceModel] deviceFound self.deviceList.length=' + self.deviceList.length);
            for (var i = 0; i < self.discoverList.length; i++) {
                if (self.discoverList[i].deviceId === data.device.deviceId) {
                    console.info('CookBook[RemoteDeviceModel] device founded, ignored');
                    return;
                }
            }
            self.discoverList[self.discoverList.length] = data.device;
            self.callback();
        });
        this.#deviceManager.on('discoverFail', (data) => {
            console.info('CookBook[RemoteDeviceModel] discoverFail data=' + JSON.stringify(data));
        });
        this.#deviceManager.on('serviceDie', () => {
            console.error('CookBook[RemoteDeviceModel] serviceDie');
        });

        SUBSCRIBE_ID = Math.floor(65536 * Math.random());
        var info = {
            subscribeId: SUBSCRIBE_ID,
            mode: 0xAA,
            medium: 2,
            freq: 2,
            isSameAccount: false,
            isWakeRemote: true,
            capability: 0
        };
        console.info('CookBook[RemoteDeviceModel] startDeviceDiscovery ' + SUBSCRIBE_ID);
        this.#deviceManager.startDeviceDiscovery(info);
    }

    authDevice(deviceId, callback) {
        console.info('CookBook[RemoteDeviceModel] authDevice ' + deviceId);
        for (var i = 0; i < this.discoverList.length; i++) {
            if (this.discoverList[i].deviceId === deviceId) {
                console.info('CookBook[RemoteDeviceModel] device founded, ignored');
                let extraInfo = {
                    "targetPkgName": 'com.ohos.distributedRemoteStartFA',
                    "appName": 'demo',
                    "appDescription": 'demo application',
                    "business": '0'
                };
                let authParam = {
                    "authType": 1,
                    "appIcon": '',
                    "appThumbnail": '',
                    "extraInfo": extraInfo
                };
                console.info('CookBook[RemoteDeviceModel] authenticateDevice ' + JSON.stringify(this.discoverList[i]));
                let self = this;
                this.#deviceManager.authenticateDevice(this.discoverList[i], authParam, (err, data) => {
                    if (err) {
                        console.info('CookBook[RemoteDeviceModel] authenticateDevice failed, err=' + JSON.stringify(err));
                        self.authCallback = null;
                    } else {
                        console.info('CookBook[RemoteDeviceModel] authenticateDevice succeed, data=' + JSON.stringify(data));
                        self.authCallback = callback;
                    }
                });
            }
        }
    }

    unregisterDeviceListCallback() {
        console.info('CookBook[RemoteDeviceModel] stopDeviceDiscovery ' + SUBSCRIBE_ID);
        this.#deviceManager.stopDeviceDiscovery(SUBSCRIBE_ID);
        this.#deviceManager.off('deviceStateChange');
        this.#deviceManager.off('deviceFound');
        this.#deviceManager.off('discoverFail');
        this.#deviceManager.off('serviceDie');
        this.deviceList = [];
    }
}

3.设备识别函数实现

效果如下:

#夏日挑战赛#【FFH】分布式任务调度流转实现(OpenHarmony JSUI)-开源基础软件社区

​ 在点击“流转FA”按钮的时候,我们要做的当然显示组网内在线设备的列表,其中这些设备需要经过安全认证,也就是我们刚刚封装的设备管理类的处理,下面我们就简单编写一下设备识别函数。

​ 核心思想是调用设备管理类的registerDeviceListCallback( )函数获取到设备列表,然后在data里面进行声明,最终在界面用Dialog组件进行渲染。

首先我们要导入上面提到的类,并且在data中创建一个设备管理器类:

import RemoteDeviceModel from"../../model/RemoteDeviceModel";
export default {
    data: {
        deviceList:[],
        remoteDeviceModel: new RemoteDeviceModel()
    },

点击屏幕上的按钮后,调用如下函数:

onContinueFAClick() {
        console.info('FSRxxxxx onContinueAbilityClick begin');
        const self = this;
        this.remoteDeviceModel.registerDeviceListCallback(() => {
            console.info('FSRxxxxx registerDeviceListCallback, callback entered');
            const list = [];
            //第一个设备默认本机
            list[0] = this.DEVICE_LIST_LOCALHOST;
            let deviceList;
            if (self.remoteDeviceModel.discoverList.length > 0) {
                deviceList = self.remoteDeviceModel.discoverList;
            } else {
                deviceList = self.remoteDeviceModel.deviceList;
            }
            console.info('FSRxxxxx on remote device updated, count=' + deviceList.length);
            for (let i = 0; i < deviceList.length; i++) {
                console.info('device ' + i + '/' + deviceList.length + ' deviceId='
                + deviceList[i].deviceId + ' deviceName=' + deviceList[i].deviceName + ' deviceType='
                + deviceList[i].deviceType);
                list[i + 1] = {
                    name: deviceList[i].deviceName,
                    id: deviceList[i].deviceId
                };
            }
            self.deviceList = list;
        });
        this.$element('continueFADialog').show();
        console.info('onContinueFAClick end');
    },

4.流转函数实现

流转的实现主要是调用@ohos.ability.featureAbility里的相关API。首先我们要导入featureAbility这个包

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

​ 接着就是流转函数的编写,在编写之前,我们现在看看几个要注意的点,我们要调用的是@ohos.ability.featureAbility中的startAbility,这个接口需要一个对象作为参数,这个对象的属性包括bundleName(可以在config.json文件中查看),abilityName以及deviceId。其中要注意的事abilityName是Package名称(可以在config.json文件中查看)加上ability的名称(这一部分区别于HarmonyOS)。如下,我的Package名称是’com.example.entry’,ability名称是’MainAbility’,那么我的ablityName这个属性就是’com.example.entry.MainAbility’

#夏日挑战赛#【FFH】分布式任务调度流转实现(OpenHarmony JSUI)-开源基础软件社区

startFAContinuation(deviceId, deviceName) {
        this.$element('continueFADialog').close();
        console.info('FSRxxxxx featureAbility.startAbility deviceId=' + deviceId
        + ' deviceName=' + deviceName);
        const wantValue = {
            bundleName: 'com.example.remotestartfademo',
            //这个要注意是packagename+ability名字
            abilityName: 'com.example.entry.MainAbility',
            deviceId: deviceId
        };
        //拉起目标设备FA
        featureAbility.startAbility({
            want: wantValue
        }).then((data) => {
            console.info('featureAbility.startAbility finished, ' + JSON.stringify(data));
        });
        //关闭本设备FA
        featureAbility.terminateSelf();
        console.info('featureAbility.startAbility want=' + JSON.stringify(wantValue));
        console.info('featureAbility.startAbility end');
    },

接着就是监听Radio组件的变化进行流转,这个方法绑定到监听Radio变化的事件上即可实现流转,代码如下:

  onRadioChange(inputValue, e) {
        console.info('FSRxxxxx onRadioChange ' + inputValue + ', ' + e.value);
        if (inputValue === e.value) {
            for (let i = 0; i < this.remoteDeviceModel.deviceList.length; i++) {
                if (this.remoteDeviceModel.deviceList[i].deviceId === e.value) {
                    this.startFAContinuation(this.remoteDeviceModel.deviceList[i].deviceId, this.remoteDeviceModel.deviceList[i].deviceName);
                }
            }
        }
    },

5.界面样式设计以及事件绑定

关闭Dialog函数:

    onDismissDialogClicked() {
        this.dismissDialog();
    },
    dismissDialog() {
        this.$element('continueFADialog').close();
        this.remoteDeviceModel.unregisterDeviceListCallback();
    },

Hml页面代码:

<div class="container">
    <input class="btn" type="button" value="流转FA" onclick="onContinueFAClick"></input>

    <dialog id="continueFADialog" class="dialogMain" oncancel="cancelDialog">
        <div class="dialogDiv">
            <text class="dialogTitleText">选择设备</text>
            <list class="dialogDeviceList" divider="true">
                <list-item for="{{ deviceList }}" class="deviceListItem">
                    <div>
                        <label class="deviceItemTitle" target="{{ $item.id }}">{{ $item.name }}</label>
                        <input class="deviceItemRadio" type="radio" checked="{{ $item.id === 'localhost' }}"
                               id="{{ $item.id }}"
                               name="radioSample" value="{{ $item.id }}"
                               onchange="onRadioChange({{ $item.id }})"></input>
                    </div>
                </list-item>
            </list>
            <div class="innerBtn">
                <button class="dialogCancelButton" type="text" value="取消" onclick="onDismissDialogClicked"></button>
            </div>
        </div>
    </dialog>
</div>

CSS页面代码:

.container {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    align-content: center;
    left: 0px;
    top: 0px;
    width: 100%;
    height: 100%;
}

.btn {
    margin-top: 40%;
    width: 70%;
    height: 100px;
    font-size: 40px;
    background-color: #2788D9;

}

.container {
    width: 100%;
    height: 100%;
    flex-direction: column;
    justify-content: space-between;
    align-items: center;
    background-image: url(common/media/bg_blurry.png);
    background-size: cover;
    background-position: center center;
}
.title {
    height: 64px;
    font-size: 48px;
    color: #FFF;
    margin-bottom: 10px;
    text-align: center;
}
.txt {
    color: #000;
    font-weight: bold;
    font-size: 39px;
}

.dialogMain {
    width: 500px;
}

.dialogDiv {
    flex-direction: column;
    align-items: center;
}

.dialogTitleText {
    width: 434px;
    height: 80px;
    font-size: 32px;
    font-weight: 600;
}

.innerBtn {
    width: 400px;
    height: 120px;
    justify-content: space-around;
    align-items: center;
}

.dialogCancelButton {
    width: 100%;
    font-size: 32px;
}

.dialogDeviceList {
    width: 434px;
    max-height: 150px;
}

.deviceListItem {
    width: 434px;
    height: 80px;
    flex-direction: row;
    align-items: center;
}


.deviceItemTitle {
    width: 80%;
    height: 80px;
    text-align: start;
}

完成上述代码后,将真机连接WiFi,然后在系统的音乐播放器上进行组网认证,再打开上述应用实例,就可以实现流转效果了。

结尾

​ 我们上面的Demo是基于FA模型的,但是最近的更新了基于API9 的Stage模型,官方给出的Stage模型的设计原因如下:

Stage模型的设计,主要是为了解决FA模型无法解决的开发场景问题,方便开发者更加方便地开发出分布式环境下的复杂应用。

#夏日挑战赛#【FFH】分布式任务调度流转实现(OpenHarmony JSUI)-开源基础软件社区

​ 比如在Stage模型中,我们要复现上面的这个demo,我们可能只需要一半甚至更少的代码量。可见,Stage模型是我们北向后面分布式开发的一个趋势。

​ 后面我们再一起来学习Stage模型开发吧。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
remoteStartFADemo.zip 5.02M 12次下载
6
收藏 5
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐