OpenHarmony3.1 分布式应用开发之分布式任务调度
主要包含启动远程设备的FA和连接远程设备的Service
HarmonyOS与openHarmony有2个重要的不同(坑点) 在本页面搜索“坑点”,即可快速知道
一)启动远程设备的FA
FA可以简单认为是页面
应用场景:设备A启动设备B的相册
1.设置签名:
为了能让dayu200开发板顺利运行应用,需要设置前面
File->Project Structure->Project->Signing configs->勾选Automatically generate signing->Apply->OK
2.创建另一个Ability
右键entry模块->New->Ability->PageAbility
3.启动本地的FA
featureAbility.startAbility({//需要导入 import featureAbility from "@ohos.ability.featureAbility"
want://跨设备启动,需要哪些参数?设备Id,app,页面名字
{
deviceId: "",//指定设备Id 本地设备Id默认空字符串
bundleName: "com.example.myohtest",//指定App看 config.json app->bundleName
abilityName: "com.example.entry.SecondAbility"//指定页面名 坑点,与HarmonyOS不同 openHarmony的这里为 config.json中的package参数+页面名
}
})
4.启动远程设备的FA
一个设备启动另一个设备的FA,首先需要在config.json中配置权限
1)非敏感权限配置在reqPermissions中,敏感权限还需要运行弹窗的方式用户授权,需要代码进行
"reqPermissions":[ //非敏感权限
{
"name":"ohos.permission.DISTRIBUTED_DATASYNC"//分布式数据同步的权限
}
]
2)敏感权限通过弹窗配置
aboutToAppear(){
requestPermission();
}
通过运行时,用户弹窗的方式,请求用户授权
在start_remote_fa.ets头部加上requestPermission方法
import bundle from '@ohos.bundle';
import abilityAccessCtrl from "@ohos.abilityAccessCtrl";
async function requestPermission() {//可直接复用,2个地方需要修改一下 下一行的授权与下2行的包名
let array: Array<string> = ["ohos.permission.DISTRIBUTED_DATASYNC"];//这部分需要修改,改成对应的授权
const appInfo = await bundle.getApplicationInfo('com.example.myohtest', 0, 100);//一参:包名, 二参:bundle的标记 ,三参:user的id
let tokenID = appInfo.accessTokenId; //令牌Id相当于安全标识
const atManager = abilityAccessCtrl.createAtManager();
let requestPermissions: Array<string> = []; //用于存放用户未授权的权限
//遍历每一个权限,判断权限是否已经通过用户授权
for (let i = 0; i < array.length; i++) {
let result = await atManager.verifyAccessToken(tokenID, array[i]); //通过tokenId判断是否有访问的权限
if (result != abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { //如果未授予该权限,则放入用户需要请求的权限列表中
requestPermissions.push(array[i]);
}
}
//没有需要授权的权限
if (requestPermissions.length == 0 || requestPermissions == []) {
return;
}
let context = featureAbility.getContext();
context.requestPermissionsFromUser(requestPermissions, 1, (data)=>{
console.info("xxxxxx data:" + JSON.stringify(data));
// console.info("data requestCode:" + data.requestCode);
// console.info("data permissions:" + data.permissions);
// console.info("data authResults:" + data.authResults);
});
}
3)FeatureAblity的DevideId如何获取?
import deviceManager from '@ohos.distributedHardware.deviceManager';
//参数1 bundleName,参数2 回调函数
deviceManager.createDeviceManager("com.example.myohtest", (err, value) => {
if (!err) {
let devManager = value;
let deviceList = devManager.getTrustedDeviceListSync();//以同步的方式获得已信任的设备列表
//把start_local_fa.ets调用本地页面的代码拷过来
featureAbility.startAbility({
want:
{
deviceId: deviceList[0].deviceId,//组网环境下只有2台设备时,那么当前设备的已信任列表只有一个设备,直接用即可
//若有多个设备,则需要弹出对话框,列出所有设备,通过单选按钮,选择想要的目标设备
bundleName: "com.example.myohtest",
abilityName: "com.example.entry.SecondAbility"
}
}).then((data) => {
console.info('xxxxxxOperation successful. Data: ' + JSON.stringify(data))
}).catch((error) => {
console.error('xxxxxxOperation failed. Cause: ' + JSON.stringify(error));
})
}
});
4) 修改config.json
“js”目录下 name为".MainAbility"的"pages"修改为"pages/start_remote_fa"
5)完整代码
a.start_local_fa.ets
import featureAbility from '@ohos.ability.featureAbility'
@Entry
@Component
struct Index {
@State message: string = 'Main'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button('启动本地设备的Second')
.width(500)
.height(80)
.fontSize(50)
.align(Alignment.Center)
.backgroundColor('#AD9D8F')
.fontColor('#FFFFFF')
.borderRadius(10)
.margin(1)
.onClick((event: ClickEvent) => {
featureAbility.startAbility({
want:
{
deviceId: "",
bundleName: "com.example.myohtest",
abilityName: "com.example.entry.SecondAbility"
}
})
});
}
.width('100%')
}
.height('100%')
}
}
b.start_remote_fa.ets
import featureAbility from '@ohos.ability.featureAbility'
import bundle from '@ohos.bundle';
import abilityAccessCtrl from "@ohos.abilityAccessCtrl";
import deviceManager from '@ohos.distributedHardware.deviceManager';
async function requestPermission() {//可直接复用,2个地方需要修改一下 下一行的授权与下2行的包名
let array: Array<string> = ["ohos.permission.DISTRIBUTED_DATASYNC"];//这部分需要修改,改成对应的授权
const appInfo = await bundle.getApplicationInfo('com.example.myohtest', 0, 100);//一参:包名, 二参:bundle的标记 ,三参:user的id
let tokenID = appInfo.accessTokenId; //令牌Id相当于安全标识
const atManager = abilityAccessCtrl.createAtManager();
let requestPermissions: Array<string> = []; //用于存放用户未授权的权限
//遍历每一个权限,判断权限是否已经通过用户授权
for (let i = 0; i < array.length; i++) {
let result = await atManager.verifyAccessToken(tokenID, array[i]); //通过tokenId判断是否有访问的权限
if (result != abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { //如果未授予该权限,则放入用户需要请求的权限列表中
requestPermissions.push(array[i]);
}
}
//没有需要授权的权限
if (requestPermissions.length == 0 || requestPermissions == []) {
return;
}
let context = featureAbility.getContext();
context.requestPermissionsFromUser(requestPermissions, 1, (data)=>{
console.info("xxxxxx data:" + JSON.stringify(data));
// console.info("data requestCode:" + data.requestCode);
// console.info("data permissions:" + data.permissions);
// console.info("data authResults:" + data.authResults);
});
}
@Entry
@Component
struct Index {
@State message: string = 'Main'
aboutToAppear(): void {
requestPermission();
}
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button('启动远程设备的Second')
.width(500)
.height(80)
.fontSize(50)
.align(Alignment.Center)
.backgroundColor('#AD9D8F')
.fontColor('#FFFFFF')
.borderRadius(10)
.margin(1)
.onClick((event: ClickEvent) => {//参数1 bundleName,参数2 回调函数
deviceManager.createDeviceManager("com.example.myohtest", (err, value) => {
if (!err) {
let devManager = value;
let deviceList = devManager.getTrustedDeviceListSync();//以同步的方式获得已信任的设备列表
//把start_local_fa.ets调用本地页面的代码拷过来
featureAbility.startAbility({
want:
{
deviceId: deviceList[0].deviceId,//组网环境下只有2台设备时,那么当前设备的已信任列表只有一个设备,直接用即可
//若有多个设备,则需要弹出对话框,列出所有设备,通过单选按钮,选择想要的目标设备
bundleName: "com.example.myohtest",
abilityName: "com.example.entry.SecondAbility"
}
}).then((data) => {
console.info('xxxxxxOperation successful. Data: ' + JSON.stringify(data))
}).catch((error) => {
console.error('xxxxxxOperation failed. Cause: ' + JSON.stringify(error));
})
}
});
})
}
.width('100%')
}
.height('100%')
}
}
二)连接远程的Service
1.连接本地的Service
Service:后台服务
1)connect_local_service.ets
featureAbility.connectAbility(
{//参数1与启动本地的FA一致
deviceId: "",
bundleName: "com.example.myohtest",
abilityName: "com.example.entry.ServiceAbility",
},
{//参数2为3个回调函数
onConnect: onConnectCallback,
onDisconnect: onDisconnectCallback,
onFailed: onFailedCallback
},
);
2)在该文件首部定义三个回调方法
import prompt from '@system.prompt';
import rpc from "@ohos.rpc";
function onConnectCallback(element, remote){
console.info('xxxxxx onConnectCallback element: ' + element);
console.info('xxxxxx onConnectCallback remote: ' + remote);
if (remote == null) {
prompt.showToast({
message: "onConnectLocalService not connected yet"
});
return;
}
let option = new rpc.MessageOption();
let data = new rpc.MessageParcel();
let reply = new rpc.MessageParcel();
data.writeInt(3);//写入数据
data.writeInt(5);
remote.sendRequest(1, data, reply, option).then((result) => {
console.info('xxxxxx sendRequest success');
let msg = reply.readInt();
prompt.showToast({
message: "onConnectLocalService connect result: " + msg,
duration: 3000 //显示时间3s
});
}).catch((e) => {
console.info('xxxxxx sendRequest error:' + e);
});
}
function onDisconnectCallback(element){
console.info('xxxxxx onDisconnectCallback')
}
function onFailedCallback(code){
console.info('xxxxxx onDisconnectCallback')
}
3) 创建ServiceAbility
右键Entry->New->Ability->ServiceAbility
import rpc from "@ohos.rpc";
class FirstServiceAbilityStub extends rpc.RemoteObject{
constructor(des) {
if (typeof des === 'string') {
super(des);
} else {
return null;
}
}
onRemoteRequest(code, data, reply, option) {
console.info("xxxxxx ServiceAbility onRemoteRequest called");
if (code === 1) {//约定好的暗号
let op1 = data.readInt();//读取数据
let op2 = data.readInt();
console.info("xxxxxx op1 = " + op1 + ", op2 = " + op2);
reply.writeInt(op1 + op2);//写入数据到reply
} else {
console.info("xxxxxx ServiceAbility unknown request code");
}
return true;
}
}
export default {
onConnect(want) {
console.info("xxxxxx ServiceAbility onConnect");
try {
let value = JSON.stringify(want);
console.info("xxxxxx ServiceAbility want:" + value);
} catch(error) {
console.info("xxxxxx ServiceAbility error:" + error);
}
return new FirstServiceAbilityStub("first ts service stub");//返回桩类
}
};
4)完整代码
import featureAbility from '@ohos.ability.featureAbility'
import prompt from '@system.prompt';
import rpc from "@ohos.rpc";
function onConnectCallback(element, remote){
console.info('xxxxxx onConnectCallback element: ' + element);
console.info('xxxxxx onConnectCallback remote: ' + remote);
if (remote == null) {
prompt.showToast({
message: "onConnectLocalService not connected yet"
});
return;
}
let option = new rpc.MessageOption();
let data = new rpc.MessageParcel();
let reply = new rpc.MessageParcel();
data.writeInt(3);
data.writeInt(5);
remote.sendRequest(1, data, reply, option).then((result) => {
console.info('xxxxxx sendRequest success');
let msg = reply.readInt();
prompt.showToast({
message: "onConnectLocalService connect result: " + msg,
duration: 3000 //显示时间3s
});
}).catch((e) => {
console.info('xxxxxx sendRequest error:' + e);
});
}
function onDisconnectCallback(element){
console.info('xxxxxx onDisconnectCallback')
}
function onFailedCallback(code){
console.info('xxxxxx onDisconnectCallback')
}
@Entry
@Component
struct Index {
@State message: string = 'Main'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button('连接本地Service')
.width(500)
.height(80)
.fontSize(50)
.align(Alignment.Center)
.backgroundColor('#AD9D8F')
.fontColor('#FFFFFF')
.borderRadius(10)
.margin(1)
.onClick((event: ClickEvent) => {
featureAbility.connectAbility(
{//参数1与启动本地的FA一致
deviceId: "",
bundleName: "com.example.myohtest",
abilityName: "com.example.entry.ServiceAbility",
},
{//参数2为3个回调函数
onConnect: onConnectCallback,
onDisconnect: onDisconnectCallback,
onFailed: onFailedCallback
},
);
})
}
.width('100%')
}
.height('100%')
}
}
2.连接远程的Service
连接本地的Service改成连接远程的Service需要改3个地方
1)添加远程授权的函数
async function requestPermission() {
let array: Array<string> = ["ohos.permission.DISTRIBUTED_DATASYNC"];
const appInfo = await bundle.getApplicationInfo('com.example.myohtest', 0, 100);
let tokenID = appInfo.accessTokenId;
const atManager = abilityAccessCtrl.createAtManager();
let requestPermissions: Array<string> = [];
for (let i = 0; i < array.length; i++) {
let result = await atManager.verifyAccessToken(tokenID, array[i]);
if (result != abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
requestPermissions.push(array[i]);
}
}
if (requestPermissions.length == 0 || requestPermissions == []) {
return;
}
let context = featureAbility.getContext();
context.requestPermissionsFromUser(requestPermissions, 1, (data)=>{
console.info("xxxxxx data:" + JSON.stringify(data));
});
}
2)添加获得远程设备的Id
deviceManager.createDeviceManager("com.example.myohtest", (err, value) => {
if (!err) {
let devManager = value;
deviceList = devManager.getTrustedDeviceListSync();
console.info('xxxxxx' + deviceList[0].deviceId);
this.startRemoteService();
}
});
async startRemoteService() {
await featureAbility.connectAbility(
{
deviceId: deviceList[0].deviceId,
bundleName: "com.example.myohtest",
abilityName: "com.example.entry.ServiceAbility"
},
{
onConnect: onConnectCallback,
onDisconnect: onDisconnectCallback,
onFailed: onFailedCallback
},
);
}
3)config.json中 Service中 加一条//必须添加,不然启动不了远程
"visible":true,
4)完整代码
import featureAbility from '@ohos.ability.featureAbility'
import prompt from '@system.prompt';
import rpc from "@ohos.rpc";
import bundle from '@ohos.bundle';
import abilityAccessCtrl from "@ohos.abilityAccessCtrl";
import deviceManager from '@ohos.distributedHardware.deviceManager';
let deviceList; //全局定义设备列表
async function requestPermission() {
let array: Array<string> = ["ohos.permission.DISTRIBUTED_DATASYNC"];
const appInfo = await bundle.getApplicationInfo('com.example.myohtest', 0, 100);
let tokenID = appInfo.accessTokenId;
const atManager = abilityAccessCtrl.createAtManager();
let requestPermissions: Array<string> = [];
for (let i = 0; i < array.length; i++) {
let result = await atManager.verifyAccessToken(tokenID, array[i]);
if (result != abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
requestPermissions.push(array[i]);
}
}
if (requestPermissions.length == 0 || requestPermissions == []) {
return;
}
let context = featureAbility.getContext();
context.requestPermissionsFromUser(requestPermissions, 1, (data)=>{
console.info("xxxxxx data:" + JSON.stringify(data));
});
}
async function onConnectCallback(element, remote){
console.info('xxxxxx onConnectCallback element: ' + element);
console.info('xxxxxx onConnectCallback remote: ' + remote);
if (remote == null) {
prompt.showToast({
message: "onConnectLocalService not connected yet"
});
return;
}
let option = new rpc.MessageOption();
let data = new rpc.MessageParcel();
let reply = new rpc.MessageParcel();
data.writeInt(3);
data.writeInt(5);
remote.sendRequest(1, data, reply, option).then((result) => {
console.info('xxxxxx sendRequest success');
let msg = reply.readInt();
prompt.showToast({
message: "onConnectLocalService connect result: " + msg,
duration: 3000
});
}).catch((e) => {
console.info('xxxxxx sendRequest error:' + e);
});
}
async function onDisconnectCallback(element){
console.info('xxxxxx onDisconnectCallback')
}
async function onFailedCallback(code){
console.info('xxxxxx onDisconnectCallback')
}
@Entry
@Component
struct Index {
@State message: string = 'Main'
aboutToAppear(): void {
requestPermission();
}
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button('连接本地Service')
.width(500)
.height(80)
.fontSize(50)
.align(Alignment.Center)
.backgroundColor('#AD9D8F')
.fontColor('#FFFFFF')
.borderRadius(10)
.margin(1)
.onClick((event: ClickEvent) => {
deviceManager.createDeviceManager("com.example.myohtest", (err, value) => {
if (!err) {
let devManager = value;
deviceList = devManager.getTrustedDeviceListSync(); //获取可信设备列表
console.info('xxxxxx' + deviceList[0].deviceId);
this.startRemoteService();
}
});
})
}
.width('100%')
}
.height('100%')
}
async startRemoteService() {
await featureAbility.connectAbility(
{
deviceId: deviceList[0].deviceId, //第一个设备
bundleName: "com.example.myohtest", //app名
abilityName: "com.example.entry.ServiceAbility" //Service
},
{
onConnect: onConnectCallback,
onDisconnect: onDisconnectCallback,
onFailed: onFailedCallback
},
);
}
}
``
## 三)分布式数据对象
分布式数据存放在内存中,如果在分布式组网环境内有多个设备,进行数据同步,那么需要让这些设备都创建分布式数据对象,并且这些对象的sessionId都是一样的。分布式数据对象的效率非常高,它存放在内存中,并没有持久化的文件或者数据库
### 1.设置sessinId
```typescript
import distributedObject from '@ohos.data.distributedDataObject';
aboutToAppear() {
//名字, 年龄, 是否可见
dod = distributedObject.createDistributedObject({name:"zhangsan", age:18, isVis: false});
console.info("xxxxxx dod.name: " + dod.name); //2种写法 .name 或者 ["name"]
console.info("xxxxxx dod.age: " + dod.age);
console.info("xxxxxx dod.isVis: " + dod.isVis);
//distributedObject.genSessionId(); 可以生成一个sessionId,但是在不同设备上生成的sessionId可能不一致,最简单的办法就是指定一个
sessionId = "11111111";//sessionId的格式为8个数字的字符串
dod.setSessionId(sessionId);
console.info("xxxxxx---sessionId:" + sessionId);
}
2.监听数据对象变更
Button('监听数据对象变更')
.width(500)
.height(80)
.fontSize(50)
.align(Alignment.Center)
.backgroundColor('#AD9D8F')
.fontColor('#FFFFFF')
.borderRadius(10)
.onClick((event: ClickEvent)=>{
dod.on("change", this.changeCallback.bind(this));//坑点
console.info("xxxxxx observe change success");
})
changeCallback(sessionId, changeData) {
if (changeData != null && changeData != undefined) {
changeData.forEach(element => {
console.info("xxxxxx---:" + "changed!" + element + "," + dod[element]);
});
}
}
坑点:发起方要在changeCallback里刷新界面,则需要将正确的this绑定给changeCallback
设备B想监听设备A,想把数据打印出来拿到对象不需要刷新界面,则不需要 bind(this)
设备B监听设备A,监听到数据的改变,且想要更新界面的数据,则需要 bind(this)
3.修改对象属性
Button('修改对象属性')
.width(500)
.height(80)
.fontSize(50)
.align(Alignment.Center)
.backgroundColor('#AD9D8F')
.fontColor('#FFFFFF')
.borderRadius(10)
.onClick((event: ClickEvent)=>{
dod.name = "lisi";
dod.age = 19;
dod.isVis = true;
console.info("xxxxxx update success");
})
4.读取对象数据
Button('读取对象属性')
.width(500)
.height(80)
.fontSize(50)
.align(Alignment.Center)
.backgroundColor('#AD9D8F')
.fontColor('#FFFFFF')
.borderRadius(10)
.onClick((event: ClickEvent)=>{
console.info("xxxxxx dod.name: " + dod.name);
console.info("xxxxxx dod.age: " + dod.age);
console.info("xxxxxx dod.isVis: " + dod.isVis);
})
5.完整代码
import distributedObject from '@ohos.data.distributedDataObject';
let dod;
let sessionId;
@Entry
@Component
struct GridExample {
aboutToAppear() {
//名字, 年龄, 是否可见
dod = distributedObject.createDistributedObject({name:"zhangsan", age:18, isVis: false});
console.info("xxxxxx dod.name: " + dod.name); //2种写法 .name 或者 ["name"]
console.info("xxxxxx dod.age: " + dod.age);
console.info("xxxxxx dod.isVis: " + dod.isVis);
//distributedObject.genSessionId(); 可以生成一个sessionId,但是在不同设备上生成的sessionId可能不一致,最简单的办法就是指定一个
sessionId = "11111111";//sessionId的格式为8个数字的字符串
dod.setSessionId(sessionId);
console.info("xxxxxx---sessionId:" + sessionId);
}
build() {
Column({ space: 20 }) {
Button('监听数据对象变更')
.width(500)
.height(80)
.fontSize(50)
.align(Alignment.Center)
.backgroundColor('#AD9D8F')
.fontColor('#FFFFFF')
.borderRadius(10)
.onClick((event: ClickEvent)=>{
dod.on("change", this.changeCallback.bind(this));
console.info("xxxxxx observe change success");
})
Button('修改对象属性')
.width(500)
.height(80)
.fontSize(50)
.align(Alignment.Center)
.backgroundColor('#AD9D8F')
.fontColor('#FFFFFF')
.borderRadius(10)
.onClick((event: ClickEvent)=>{
dod.name = "lisi";
dod.age = 19;
dod.isVis = true;
console.info("xxxxxx update success");
})
Button('再次修改对象属性')
.width(500)
.height(80)
.fontSize(50)
.align(Alignment.Center)
.backgroundColor('#AD9D8F')
.fontColor('#FFFFFF')
.borderRadius(10)
.onClick((event: ClickEvent)=>{
dod.name = "wangwu";
dod.age = 20;
dod.isVis = false;
console.info("xxxxxx update success");
})
Button('读取对象属性')
.width(500)
.height(80)
.fontSize(50)
.align(Alignment.Center)
.backgroundColor('#AD9D8F')
.fontColor('#FFFFFF')
.borderRadius(10)
.onClick((event: ClickEvent)=>{
console.info("xxxxxx dod.name: " + dod.name);
console.info("xxxxxx dod.age: " + dod.age);
console.info("xxxxxx dod.isVis: " + dod.isVis);
})
}.width('100%').margin({ top: 10 })
}
changeCallback(sessionId, changeData) {
if (changeData != null && changeData != undefined) {
changeData.forEach(element => {
console.info("xxxxxx---:" + "changed!" + element + "," + dod[element]);
});
}
}
}
四)可参考的案例
五)小技巧
1.格式化代码
右击文件->Reformat Code 12:29
2.openHarmony的代码可以直接跑到HarmonyOS上吗?
可以,分布式数据对象HarmonyOS没有,但是import部分代码需要修改
3.代码对齐
右键ets文件,点击Reformat Code
快捷键 Ctrl+Alt+L
4.ets的2个等号与3个等号的区别
1.===:三个等号我们称为等同符,当等号两边的值为相同类型的时候,直接比较等号两边的值,值相同则返回true,若等号两边的值类型不同时直接返回false。
例:100===“100” //返回false
abc===“abc” //返回false
‘abc’===“abc” //返回true
NaN===NaN //返回false
false===false //返回true
2.==:两个等号我们称为等值符,当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。
类型转换规则: 1)如果等号两边是boolean、string、number三者中任意两者进行比较时,优先转换为数字进行比较。 2)如果等号两边出现了null或undefined,null和undefined除了和自己相等,就彼此相等
100==“100” //返回true
1==true //返回true
“1”==“01” //返回false,此处等号两边值得类型相同,不要再转换类型了!!
NaN==NaN //返回false,NaN和所有值包括自己都不相等。
5.设备出现异常或者运行结果出现异常
建议重启,重刷系统
https://ost.51cto.com/#bkwz