Demo效果展示
我们这次实操的最终效果就像上面一样,就是在设备A的屏幕上点击“流转FA”按钮,就终止设备A的FA并且拉起设备B的FA。
我们先分解一下这个demo的开发步骤,首先就是点击按钮需要识别到组网内的设备,然后进行设备选择,最后就实现流转。内容很简单,关键的就是在设备识别,以及流转API调用。下面我们来看看实际开发。
具体实现
具体的开发可以分为一下几个关键步骤:
- 获取分布式数据同步权限
- 设备管理类封装
- 设备识别函数实现以及流转函数实现
- 编写界面样式以及事件绑定
1、获取分布式数据同步权限
如果应用程序需要某些权限,例如存储、位置信息和日志访问权限,则应用程序必须向终端用户请求对应权限。确定需要的权限后,在config.json文件中添加权限。本例中,需要获取分布式数据同步权限,因此,需要在config.json中请求添加分布式数据同步权限,修改config.json文件如下:
"reqPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
}
]
代码段具体添加的位置如下图:
同时,在页面代码段中申请用户权限:
编写一个申请权限函数,然后再初始化页面时调用,具体代码如下所示。
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上的设备管理类,创建远程设备模型来进行操作。
我们先像上图一样创建一个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、设备识别函数实现
效果如下:
在点击“流转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’。
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模型无法解决的开发场景问题,方便开发者更加方便地开发出分布式环境下的复杂应用。
比如在Stage模型中,我们要复现上面的这个demo,我们可能只需要一半甚至更少的代码量。可见,Stage模型是我们北向后面分布式开发的一个趋势。
后面我们再一起来学习Stage模型开发吧。