分布式计算器
介绍
本示例使用分布式能力实现了一个简单的计算器应用,可以进行简单的数值计算,支持远程拉起另一个设备的计算器应用,两个计算器应用进行协同计算。
远程拉起:通过StartAbility实现远端应用的拉起。
协同计算:通过DistributedDataKit分布式数据框架实现异端应用的数据同步。
本示例用到了媒体查询接口[@ohos.mediaquery]
分布式设备管理能力接口(设备管理),实现设备之间的kvStore对象的数据传输交互[@ohos.distributedHardware.deviceManager]
分布式数据管理接口[@ohos.data.distributedData]
效果预览
使用说明
1.点击桌面应用图标,启动应用。
2.点击应用右上角按钮,或者在界面任意位置滑动(上下左右滑动皆可)即可弹出设备选择框。
3.在设备选择框中点击对端设备名称,拉起对端应用。
4.对端应用启动后,可在任意一端中操作应用,两端应用可实现数据实时同步。
5.在设备选择框中选中本机即可关闭对端应用。
相关概念
鸿蒙开发文档参考 :[gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
]
数据管理实例: 用于获取KVStore的相关信息。
单版本分布式数据库:继承自KVStore,不对数据所属设备进行区分,提供查询数据和同步数据的方法。
具体实现
在分布式计算器应用中,分布式设备管理包含了分布式设备搜索、分布式设备列表弹窗、远端设备拉起三部分。
首先在分布式组网内搜索设备,然后把设备展示到分布式设备列表弹窗中,最后根据用户的选择拉起远端设备。
分布式设备搜索
通过SUBSCRIBE_ID搜索分布式组网内的远端设备,详见startDeviceDiscovery(){}模块[源码参考]。
- Copyright (c) 2022 Huawei Device Co., Ltd.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
*/
import deviceManager from '@ohos.distributedDeviceManager';
import Logger from '../model/Logger'
import { Callback } from '@ohos.base'
interface deviceData {
device: deviceManager.DeviceBasicInfo
}
interface extraInfo {
bindType: number
targetPkgName: string
appName: string
}
const TAG: string = 'RemoteDeviceModel'
let SUBSCRIBE_ID: number = 100
export const BUNDLE_NAME: string = 'ohos.samples.distributedcalc'
export class RemoteDeviceModel {
public deviceList: Array< deviceManager.DeviceBasicInfo > | null = []
public discoverList: Array< deviceManager.DeviceBasicInfo > = []
private callback: () = > void = () = > {
}
private authCallback: () = > void = () = > {
}
private deviceManager: deviceManager.DeviceManager | undefined = undefined
registerDeviceListCallback(callback: Callback< void >) {
Logger.info(TAG, `deviceManager type =${typeof (this.deviceManager)} ,${JSON.stringify(this.deviceManager)} ,${JSON.stringify(this.deviceManager) === '{}'}`)
if (typeof (this.deviceManager) !== 'undefined') {
this.registerDeviceListCallbackImplement(callback)
return
}
Logger.info(TAG, 'deviceManager.createDeviceManager begin')
try {
let dmInstance = deviceManager.createDeviceManager(BUNDLE_NAME);
this.deviceManager = dmInstance
this.registerDeviceListCallbackImplement(callback)
Logger.info(TAG, `createDeviceManager callback returned, value= ${JSON.stringify(this.deviceManager)}`)
} catch (error) {
Logger.error(TAG, `createDeviceManager throw code:${error.code} message:${error.message}`)
}
Logger.info(TAG, 'deviceManager.createDeviceManager end')
}
changeStateOnline(device: deviceManager.DeviceBasicInfo) {
if (this.deviceList !== null) {
this.deviceList![this.deviceList!.length] = device;
}
Logger.debug(TAG, `online, device list= ${JSON.stringify(this.deviceList)}`);
this.callback();
if (this.authCallback !== null) {
this.authCallback();
this.authCallback = () = > {
}
}
}
changeStateOffline(device: deviceManager.DeviceBasicInfo) {
if (this.deviceList !== null && this.deviceList!.length > 0) {
let list: Array< deviceManager.DeviceBasicInfo > = [];
for (let j = 0; j < this.deviceList!.length; j++) {
if (this.deviceList![j].deviceId !== device.deviceId) {
list[j] = device;
}
}
this.deviceList = list;
}
Logger.info(TAG, `offline, updated device list=${JSON.stringify(device)}`);
this.callback();
}
changeState(device: deviceManager.DeviceBasicInfo, state: number) {
if (this.deviceList !== null && this.deviceList!.length <= 0) {
this.callback();
return;
}
if (this.deviceList !== null && state === deviceManager.DeviceStateChange.AVAILABLE) {
let list: Array< deviceManager.DeviceBasicInfo > = new Array();
for (let i = 0; i < this.deviceList!.length; i++) {
if (this.deviceList![i].deviceId !== device.deviceId) {
list[i] = device;
}
}
this.deviceList = list;
Logger.debug(TAG, `ready, device list= ${JSON.stringify(device)}`);
this.callback();
} else {
if (this.deviceList !== null) {
for (let j = 0; j < this.deviceList!.length; j++) {
if (this.deviceList![j].deviceId === device.deviceId) {
this.deviceList![j] = device;
break;
}
}
Logger.debug(TAG, `offline, device list= ${JSON.stringify(this.deviceList)}`);
this.callback();
}
}
}
registerDeviceListCallbackImplement(callback: Callback< void >) {
Logger.info(TAG, 'registerDeviceListCallback')
this.callback = callback
if (this.deviceManager === undefined) {
Logger.error(TAG, 'deviceManager has not initialized')
this.callback()
return
}
Logger.info(TAG, 'getTrustedDeviceListSync begin')
try {
let list = this.deviceManager !== undefined ? this.deviceManager.getAvailableDeviceListSync() : null;
Logger.debug(TAG, `getTrustedDeviceListSync end, deviceList= ${JSON.stringify(list)}`);
if (typeof (list) !== 'undefined' && JSON.stringify(list) !== '[]') {
this.deviceList = list!;
}
Logger.info(TAG, `getTrustedDeviceListSync end, deviceList=${JSON.stringify(list)}`);
} catch (error) {
Logger.error(TAG, `getTrustedDeviceListSync throw code:${error.code} message:${error.message}`);
}
this.callback();
Logger.info(TAG, 'callback finished');
try {
if (this.deviceManager !== undefined) {
this.deviceManager.on('deviceStateChange', (data) = > {
if (data === null) {
return
}
Logger.debug(TAG, `deviceStateChange data= ${JSON.stringify(data)}`)
switch (data.action) {
case deviceManager.DeviceStateChange.AVAILABLE:
this.changeState(data.device, deviceManager.DeviceStateChange.AVAILABLE)
break
case deviceManager.DeviceStateChange.UNKNOWN:
this.changeStateOnline(data.device)
break
case deviceManager.DeviceStateChange.UNAVAILABLE:
this.changeStateOffline(data.device)
break
default:
break
}
})
}
if (this.deviceManager !== undefined) {
this.deviceManager.on('discoverSuccess', (data) = > {
if (data === null) {
return
}
this.discoverList = []
Logger.info(TAG, `discoverSuccess data=${JSON.stringify(data)}`)
this.deviceFound(data.device)
})
this.deviceManager.on('discoverFailure', (data) = > {
Logger.info(TAG, `discoverFailure data= ${JSON.stringify(data)}`)
})
this.deviceManager.on('serviceDie', () = > {
Logger.error(TAG, 'serviceDie')
})
}
} catch (error) {
Logger.error(TAG, `on throw code:${error.code} message:${error.message}`)
}
this.startDeviceDiscovery()
}
deviceFound(data: deviceManager.DeviceBasicInfo) {
for (let i = 0;i < this.discoverList.length; i++) {
if (this.discoverList[i].deviceId === data.deviceId) {
Logger.info(TAG, 'device founded ignored')
return
}
}
this.discoverList[this.discoverList.length] = data
Logger.debug(TAG, `deviceFound self.discoverList= ${this.discoverList}`)
this.callback()
}
/**
- 通过SUBSCRIBE_ID搜索分布式组网内的设备
*/
startDeviceDiscovery() {
let discoverParam: Record< string, number > = {
'discoverTargetType': 1
};
let filterOptions: Record< string, number > = {
'availableStatus': 0,
};
Logger.info(TAG, `startDeviceDiscovery${SUBSCRIBE_ID}`);
try {
if (this.deviceManager !== undefined) {
this.deviceManager.startDiscovering(discoverParam, filterOptions)
}
} catch (error) {
Logger.error(TAG, `startDeviceDiscovery throw code:${error.code} message:${error.message}`)
}
}
unregisterDeviceListCallback() {
Logger.debug(TAG, `stopDeviceDiscovery ${SUBSCRIBE_ID}`)
if (this.deviceManager === undefined) {
return
}
if (this.deviceManager !== undefined) {
try {
Logger.info(TAG, `stopDiscovering`)
this.deviceManager.stopDiscovering();
} catch (error) {
Logger.error(TAG, `stopDeviceDiscovery throw code:${JSON.stringify(error.code)} message:${error.message}`)
}
try {
this.deviceManager.off('deviceStateChange')
this.deviceManager.off('discoverSuccess')
this.deviceManager.off('discoverFailure')
this.deviceManager.off('serviceDie')
} catch (error) {
Logger.error(TAG, `off throw code:${error.code} message:${error.message}`)
}
}
this.deviceList = []
this.discoverList = []
}
authenticateDevice(device: deviceManager.DeviceBasicInfo, callBack: Callback< void >) {
Logger.info(TAG, `authenticateDevice ${JSON.stringify(device)}`)
for (let i = 0; i < this.discoverList.length; i++) {
if (this.discoverList[i].deviceId !== device.deviceId) {
continue
}
if (this.deviceManager === undefined) {
return
}
try {
if (this.deviceManager !== undefined) {
this.deviceManager.bindTarget(device.deviceId, {
bindType: 1,
targetPkgName: BUNDLE_NAME,
appName: 'Distributed distributecalc',
}, (err, data) = > {
if (err) {
Logger.error(TAG, `authenticateDevice error: ${JSON.stringify(err)}`)
this.authCallback = () = > {
}
return
}
Logger.debug(TAG, `authenticateDevice succeed: ${JSON.stringify(data)}`)
this.authCallback = callBack
})
}
} catch (error) {
Logger.error(TAG, `authenticateDevice throw throw code:${error.code} message:${error.message}`)
}
}
}
}
分布式设备列表弹窗
使用@CustomDialog装饰器来装饰分布式设备列表弹窗,[源码参考]。
/*
* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import deviceManager from '@ohos.distributedDeviceManager';
import Logger from '../model/Logger'
const TAG: string = 'DeviceDialog'
@CustomDialog
export struct DeviceDialog {
controller?: CustomDialogController;
@StorageLink('deviceList') deviceList: Array< deviceManager.DeviceBasicInfo > = AppStorage.get('deviceList')!;
private selectedIndex: number | undefined = 0;
private onSelectedIndexChange: (selectedIndex: number | undefined) = > void = () = > {
}
@State deviceDialogWidth: number = 0
build() {
Column() {
Text($r('app.string.choiceDevice'))
.fontSize(px2vp(30))
.width('100%')
.height('20%')
.fontColor(Color.Black)
.textAlign(TextAlign.Start)
List() {
ForEach(this.deviceList, (item: deviceManager.DeviceBasicInfo, index: number | undefined) = > {
ListItem() {
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
Text(item.deviceName)
.fontSize(px2vp(30))
.width('80%')
.fontColor(Color.Black)
Radio({ value: '', group: 'radioGroup' })
.radioStyle({
checkedBackgroundColor: '#ff0d64fb'
})
.align(Alignment.Top)
.width('3%')
.checked(index === this.selectedIndex ? true : false)
}
.margin({ top: 17 })
.onClick(() = > {
Logger.debug(TAG, `select device: ${item.deviceId}`)
Logger.debug(TAG, `deviceList: ${JSON.stringify(this.deviceList)}`)
if (this.selectedIndex !== undefined && index === this.selectedIndex) {
Logger.info(TAG, `index:${JSON.stringify(index)} ty:${JSON.stringify(typeof (index))} this.selectedIndex:${JSON.stringify(this.selectedIndex)} ${JSON.stringify(typeof (this.selectedIndex))}`)
return
} else if (this.selectedIndex !== undefined) {
this.selectedIndex = index
this.onSelectedIndexChange(this.selectedIndex)
}
})
}
.width('100%')
.height('40%')
}, (item: deviceManager.DeviceBasicInfo) = > item.deviceName)
}
.height('60%')
.width('100%')
.layoutWeight(1)
Button() {
Text($r('app.string.cancel'))
.width('90%')
.fontSize(21)
.fontColor('#ff0d64fb')
.textAlign(TextAlign.Center)
}
.type(ButtonType.Capsule)
.backgroundColor(Color.White)
.onClick(() = > {
if (this.controller !== undefined) {
this.controller.close()
}
})
}
.margin({ bottom: 15 })
.onAreaChange((oldArea: Area, newArea: Area) = > {
this.deviceDialogWidth = (newArea.width > newArea.height ? newArea.height : newArea.width) as number * 0.1 //percentage
})
.width('80%')
.height(px2vp(240))
.padding({ left: 18, right: 32 })
.backgroundColor(Color.White)
.border({ color: Color.White, radius: 20 })
}
}
远端设备拉起
通过startAbility(deviceId)方法拉起远端设备的包,[源码参考]。
/*
* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import deviceManager from '@ohos.distributedDeviceManager';
import Logger from '../model/Logger'
import { DeviceDialog } from '../common/DeviceDialog'
import { RemoteDeviceModel, BUNDLE_NAME } from '../model/RemoteDeviceModel'
import common from '@ohos.app.ability.common'
import Want from '@ohos.app.ability.Want';
const TAG: string = 'TitleBar'
const DATA_CHANGE: string = 'dataChange'
const EXIT: string = 'exit'
const DEVICE_DISCOVERY_RANGE: number = 1000
@Component
export struct TitleBarComponent {
@Prop isLand: boolean | null = null
@State selectedIndex: number | undefined = 0
@StorageLink('deviceList') deviceList: Array< deviceManager.DeviceBasicInfo > = []
@Link isDistributed: boolean
private isShow: boolean = false
private startAbilityCallBack: (key: string) = > void = () = > {
}
private dialogController: CustomDialogController | null = null
private remoteDeviceModel: RemoteDeviceModel = new RemoteDeviceModel()
onSelectedIndexChange = async (index: number | undefined) = > {
Logger.info(TAG, `selectedIndexChange`)
this.selectedIndex = index
if (this.selectedIndex === 0) {
Logger.info(TAG, `stop ability`)
await this.startAbilityCallBack(EXIT)
this.isDistributed = false
this.deviceList = []
if (this.dialogController !== null) {
this.dialogController.close()
}
return
}
this.selectDevice()
}
aboutToAppear() {
AppStorage.setOrCreate('deviceList', this.deviceList)
}
clearSelectState() {
this.deviceList = []
if (this.dialogController !== null) {
this.dialogController.close()
}
Logger.info(TAG, `cancelDialog`)
if (this.remoteDeviceModel === undefined) {
return
}
this.remoteDeviceModel.unregisterDeviceListCallback()
}
selectDevice() {
Logger.info(TAG, `start ability ......`)
this.isDistributed = true
if (this.selectedIndex !== undefined && (this.remoteDeviceModel === null || this.remoteDeviceModel.discoverList.length <= 0)) {
Logger.info(TAG, `continue unauthed device: ${JSON.stringify(this.deviceList)}`)
this.startAbility(this.deviceList[this.selectedIndex].networkId)
this.clearSelectState()
return
}
Logger.info(TAG, `start ability1, needAuth:`)
if (this.selectedIndex !== undefined) {
this.remoteDeviceModel.authenticateDevice(this.deviceList[this.selectedIndex], () = > {
Logger.info(TAG, `auth and online finished`);
if (this.remoteDeviceModel !== null && this.remoteDeviceModel.deviceList !== null && this.selectedIndex !== undefined) {
for (let i = 0; i < this.remoteDeviceModel.deviceList!.length; i++) {
if (this.remoteDeviceModel.deviceList![i].deviceName === this.deviceList[this.selectedIndex].deviceName) {
this.startAbility(this.remoteDeviceModel.deviceList![i].networkId);
}
}
}
})
}
Logger.info(TAG, `start ability2 ......`)
this.clearSelectState()
}
async startAbility(deviceId: string | undefined) {
Logger.debug(TAG, `startAbility deviceId: ${deviceId}`)
let context = getContext(this) as common.UIAbilityContext
let want: Want = {
bundleName: BUNDLE_NAME,
abilityName: 'MainAbility',
deviceId: deviceId,
parameters: {
isRemote: 'isRemote'
}
}
context.startAbility(want).then((data) = > {
Logger.info(TAG, `start ability finished: ${JSON.stringify(data)}`)
this.startAbilityCallBack(DATA_CHANGE)
})
}
showDiainfo() {
this.deviceList = []
// 注册监听回调,发现设备或查找到已认证设备会弹窗显示
this.remoteDeviceModel.registerDeviceListCallback(() = > {
this.deviceList = []
Logger.info(TAG, `registerDeviceListCallback, callback entered`)
let context: common.UIAbilityContext | undefined = AppStorage.get('UIAbilityContext')
if (context !== undefined) {
this.deviceList.push({
deviceId: '0',
deviceName: context.resourceManager.getStringSync($r('app.string.localhost').id),
deviceType: '0',
networkId: ''
})
}
let deviceTempList = this.remoteDeviceModel.discoverList.length > 0 ? this.remoteDeviceModel.discoverList : this.remoteDeviceModel.deviceList;
if (deviceTempList !== null) {
for (let i = 0; i < deviceTempList!.length; i++) {
Logger.debug(TAG, `device ${i}/${deviceTempList!.length} deviceId= ${deviceTempList![i].deviceId},
deviceName= ${deviceTempList![i].deviceName}, deviceType= ${deviceTempList![i].deviceType}`);
if (deviceTempList !== null) {
this.deviceList.push({
deviceId: deviceTempList![i].deviceId,
deviceName: deviceTempList![i].deviceName,
deviceType: deviceTempList![i].deviceType,
networkId: deviceTempList![i].networkId,
})
AppStorage.set('deviceList', this.deviceList)
}
}
}
})
if (this.dialogController === null) {
this.dialogController = new CustomDialogController({
builder: DeviceDialog({
selectedIndex: this.selectedIndex,
onSelectedIndexChange: this.onSelectedIndexChange
}),
cancel: () = > {
this.clearSelectState()
},
autoCancel: true,
alignment: this.isLand ? DialogAlignment.Center : DialogAlignment.Bottom,
customStyle: false
})
}
if (this.dialogController !== null) {
this.dialogController.open()
}
}
build() {
Row() {
Image($r('app.media.ic_back'))
.height('60%')
.margin({ left: '5%' })
.width('50px')
.objectFit(ImageFit.Contain)
.onClick(async () = > {
let context = getContext(this) as common.UIAbilityContext
context.terminateSelf()
})
Text($r('app.string.distributed_calculator'))
.height('60%')
.fontSize('28px')
.margin({ left: 12 })
Blank().layoutWeight(1)
if (!this.isShow) {
Image($r("app.media.ic_hop_normal1"))
.id('selectDevice')
.margin({ right: 32 })
.width('9%')
.margin({ right: '12%' })
.objectFit(ImageFit.Contain)
.onClick(() = > {
this.showDiainfo()
})
}
}
.width('100%')
.height(this.isLand ? '10%' : '6%')
.constraintSize({ minHeight: 50 })
.alignItems(VerticalAlign.Center)
}
}
分布式数据管理
(1) 管理分布式数据库
创建一个KVManager对象实例,用于管理分布式数据库对象。通过distributedData.createKVManager(config),并通过指定Options和storeId,创建并获取KVStore数据库,并通过Promise方式返回,此方法为异步方法,例如this.kvManager.getKVStore(STORE_ID, options).then((store) => {}),[源码参考]。
/*
* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import distributedData from '@ohos.data.distributedKVStore';
import Logger from '../model/Logger'
import { BUNDLE_NAME } from './RemoteDeviceModel'
import common from '@ohos.app.ability.common';
import { Callback } from '@ohos.base';
const TAG: string = 'KvStoreModel'
const STORE_ID: string = 'distributedcalc'
export class KvStoreModel {
public kvManager: distributedData.KVManager | undefined = undefined
public kvStore: distributedData.SingleKVStore | undefined = undefined
async createKvStore(context: common.BaseContext, callback: Callback< void >) {
if ((typeof (this.kvStore) !== 'undefined')) {
callback()
return
}
let config: distributedData.KVManagerConfig = {
bundleName: BUNDLE_NAME,
context: context
};
try {
Logger.info(TAG, `ecreateKVManager success`);
this.kvManager = distributedData.createKVManager(config);
} catch (err) {
Logger.info(TAG, `ecreateKVManager err:${JSON.stringify(err)}`);
}
Logger.info(TAG, `createKVManager begin`);
let options: distributedData.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedData.KVStoreType.DEVICE_COLLABORATION,
securityLevel: distributedData.SecurityLevel.S1
};
Logger.info(TAG, `kvManager.getKVStore begin`);
if (this.kvManager !== undefined) {
this.kvManager.getKVStore(STORE_ID, options, (err, store: distributedData.SingleKVStore) = > {
Logger.info(TAG, `getKVStore success, kvStore= ${store}`);
this.kvStore = store;
callback();
})
}
Logger.info(TAG, `createKVManager end`)
}
deleteKvStore() {
if (this.kvStore !== undefined && this.kvStore !== null) {
return;
}
try {
if (this.kvManager !== undefined) {
Logger.info(TAG, 'deleteKvStore success')
this.kvManager.deleteKVStore(BUNDLE_NAME, STORE_ID)
}
} catch (err) {
Logger.error(TAG, 'deleteKvStore error error is:' + JSON.stringify(err))
}
}
put(key: string, value: string) {
if (this.kvStore) {
Logger.debug(TAG, `kvStore.put ${key} = ${value}`)
this.kvStore.put(
key,
value
).then((data) = > {
Logger.debug(TAG, `kvStore.put ${key} finished, data= ${JSON.stringify(data)}`)
}).catch((err: object) = > {
Logger.debug(TAG, `kvStore.put ${key} failed, ${JSON.stringify(err)}`)
})
}
}
setOnMessageReceivedListener(context: common.UIAbilityContext, msg: string, callback: Callback< string >) {
Logger.info(TAG, `setOnMessageReceivedListener: ${msg}`);
this.createKvStore(context, () = > {
Logger.info(TAG, `kvStore.on(dataChange) begin`);
if (this.kvStore !== undefined && this.kvStore !== null) {
try {
this.kvStore!.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_REMOTE, (data) = > {
Logger.debug(TAG, `dataChange, ${JSON.stringify(data)}`);
let entries = data.insertEntries.length > 0 ? data.insertEntries : data.updateEntries;
for (let i = 0; i < entries.length; i++) {
if (entries[i].key === msg) {
let value = entries[i].value.value.toString();
Logger.debug(TAG, `Entries receive msg :${msg}, value:${value}`);
callback(value);
return;
}
}
})
} catch (err) {
Logger.error(TAG, `kvStore.on(dataChange) err :` + err);
}
}
Logger.info(TAG, `kvStore.on(dataChange) end`);
})
}
}
(2) 订阅分布式数据变化
通过订阅分布式数据库所有(本地及远端)数据变化实现数据协同,[源码参考]。
/*
* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import distributedData from '@ohos.data.distributedKVStore';
import Logger from '../model/Logger'
import { BUNDLE_NAME } from './RemoteDeviceModel'
import common from '@ohos.app.ability.common';
import { Callback } from '@ohos.base';
const TAG: string = 'KvStoreModel'
const STORE_ID: string = 'distributedcalc'
export class KvStoreModel {
public kvManager: distributedData.KVManager | undefined = undefined
public kvStore: distributedData.SingleKVStore | undefined = undefined
async createKvStore(context: common.BaseContext, callback: Callback< void >) {
if ((typeof (this.kvStore) !== 'undefined')) {
callback()
return
}
let config: distributedData.KVManagerConfig = {
bundleName: BUNDLE_NAME,
context: context
};
try {
Logger.info(TAG, `ecreateKVManager success`);
this.kvManager = distributedData.createKVManager(config);
} catch (err) {
Logger.info(TAG, `ecreateKVManager err:${JSON.stringify(err)}`);
}
Logger.info(TAG, `createKVManager begin`);
let options: distributedData.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedData.KVStoreType.DEVICE_COLLABORATION,
securityLevel: distributedData.SecurityLevel.S1
};
Logger.info(TAG, `kvManager.getKVStore begin`);
if (this.kvManager !== undefined) {
this.kvManager.getKVStore(STORE_ID, options, (err, store: distributedData.SingleKVStore) = > {
Logger.info(TAG, `getKVStore success, kvStore= ${store}`);
this.kvStore = store;
callback();
})
}
Logger.info(TAG, `createKVManager end`)
}
deleteKvStore() {
if (this.kvStore !== undefined && this.kvStore !== null) {
return;
}
try {
if (this.kvManager !== undefined) {
Logger.info(TAG, 'deleteKvStore success')
this.kvManager.deleteKVStore(BUNDLE_NAME, STORE_ID)
}
} catch (err) {
Logger.error(TAG, 'deleteKvStore error error is:' + JSON.stringify(err))
}
}
put(key: string, value: string) {
if (this.kvStore) {
Logger.debug(TAG, `kvStore.put ${key} = ${value}`)
this.kvStore.put(
key,
value
).then((data) = > {
Logger.debug(TAG, `kvStore.put ${key} finished, data= ${JSON.stringify(data)}`)
}).catch((err: object) = > {
Logger.debug(TAG, `kvStore.put ${key} failed, ${JSON.stringify(err)}`)
})
}
}
setOnMessageReceivedListener(context: common.UIAbilityContext, msg: string, callback: Callback< string >) {
Logger.info(TAG, `setOnMessageReceivedListener: ${msg}`);
this.createKvStore(context, () = > {
Logger.info(TAG, `kvStore.on(dataChange) begin`);
if (this.kvStore !== undefined && this.kvStore !== null) {
try {
this.kvStore!.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_REMOTE, (data) = > {
Logger.debug(TAG, `dataChange, ${JSON.stringify(data)}`);
let entries = data.insertEntries.length > 0 ? data.insertEntries : data.updateEntries;
for (let i = 0; i < entries.length; i++) {
if (entries[i].key === msg) {
let value = entries[i].value.value.toString();
Logger.debug(TAG, `Entries receive msg :${msg}, value:${value}`);
callback(value);
return;
}
}
})
} catch (err) {
Logger.error(TAG, `kvStore.on(dataChange) err :` + err);
}
}
Logger.info(TAG, `kvStore.on(dataChange) end`);
})
}
}
计算器模块
1、监听变化:通过this.listener.on('change', this.onLand)监听当前设备按钮状态,当改变时通过getContext(this).requestPermissionsFromUser(['ohos.permission.DISTRIBUTED_DATASYNC'])获取不同设备间的数据交换权限。
2、判断设备状态:当AppStorage.Get('isRemote')==='isRemote'时,将isDistributed状态置为true。 3、订阅分布式数据变化: 通过kvStoreModel.setOnMessageReceivedListener(DATA_CHANGE, (value) => {},其中根据isDistributed的值决定如何操作分布式计算器:为true时且输入的值不是EXIT状态把值放进expression中进行数据计算,当输入的值为空时,将expression的值置空。
4、特殊功能按钮:
- 当用户点击C按钮,表达式和运算结果归0。 将this.expression = ''; this.result = '';[源码参考]。
/*
* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import mediaQuery from '@ohos.mediaquery'
import Logger from '../model/Logger'
import { ButtonComponent } from '../common/ButtonComponent'
import { ButtonComponentHorizontal } from '../common/ButtonComponentHorizontal'
import { InputComponent } from '../common/InputComponent'
import { KvStoreModel } from '../model/KvStoreModel'
import { RemoteDeviceModel } from '../model/RemoteDeviceModel'
import { TitleBarComponent } from '../common/TitleBarComponent'
import { isOperator, calc } from '../model/Calculator'
import abilityAccessCtrl from '@ohos.abilityAccessCtrl'
import common from '@ohos.app.ability.common';
import mediaquery from '@ohos.mediaquery';
const TAG: string = 'Index'
const EXIT: string = 'exit'
const DATA_CHANGE: string = 'dataChange'
@Entry
@Component
struct Index {
@State isLand: boolean = false
@State result: string = ''
@State @Watch('dataChange') expression: string = ''
@State isDistributed: boolean = false
@State isShow: boolean = false
private listener = mediaQuery.matchMediaSync('screen and (min-aspect-ratio: 1.5) or (orientation: landscape)')
private kvStoreModel: KvStoreModel = new KvStoreModel()
private remoteDeviceModel: RemoteDeviceModel = new RemoteDeviceModel()
onLand = (mediaQueryResult: mediaquery.MediaQueryResult) = > {
Logger.debug(TAG, `onLand: mediaQueryResult.matches= ${mediaQueryResult.matches}`)
if (mediaQueryResult.matches) {
this.isLand = true
} else {
this.isLand = false
}
}
dataChange() {
Logger.info(TAG, `dataChange, expression = ${this.expression}`)
this.kvStoreModel.put(DATA_CHANGE, this.expression)
}
isOperator(operator: string) {
return (
operator === '+' || operator === '-' || operator === '*' || operator === '/'
)
}
onInputValue = (value: string) = > {
Logger.info(TAG, `this.isLand=${this.isLand}`);
if (value === 'C') { // 当用户点击C按钮,表达式和运算结果归0
this.expression = '';
this.result = '';
return;
} else if (value === 'D') {
this.expression = this.expression.substring(0, this.expression.length - 1);
this.result = this.result = calc(this.expression);
if (!this.expression.length) {
this.result = '';
Logger.info(TAG, `handleBackspace`);
}
} else if (isOperator(value)) {
Logger.info(TAG, `value=${value}`);
let size = this.expression.length;
if (size) {
const last = this.expression.charAt(size - 1);
if (isOperator(last)) {
this.expression = this.expression.substring(0, this.expression.length - 1);
}
}
if (!this.expression && (value === '*' || value === '/')) {
return;
}
this.expression += value;
} else if (value === '=') {
this.result = calc(this.expression);
if (this.result !== '' && this.result !== undefined) {
this.expression = this.result;
this.result = '';
}
} else {
this.expression += value;
this.result = calc(this.expression);
}
}
aboutToDisappear() {
Logger.info(TAG, `index disappear`)
this.kvStoreModel.deleteKvStore()
}
async aboutToAppear() {
this.listener.on('change', this.onLand)
let context = getContext(this) as common.UIAbilityContext
let atManager = abilityAccessCtrl.createAtManager()
try {
atManager.requestPermissionsFromUser(context, ['ohos.permission.DISTRIBUTED_DATASYNC']).then((data) = > {
Logger.info(TAG, `data: ${JSON.stringify(data)}`)
}).catch((err: object) = > {
Logger.info(TAG, `err: ${JSON.stringify(err)}`)
})
} catch (err) {
Logger.info(TAG, `catch err- >${JSON.stringify(err)}`)
}
Logger.info(TAG, `grantPermission,requestPermissionsFromUser`)
let isRemote: string | undefined = AppStorage.get('isRemote')
if (isRemote === 'isRemote' ? true : false) {
this.isDistributed = true
this.isShow = true
}
this.kvStoreModel.setOnMessageReceivedListener(context, DATA_CHANGE, (value: string) = > {
Logger.debug(TAG, `DATA_CHANGE: ${value},this.isDistributed = ${this.isDistributed}`)
if (this.isDistributed) {
if (value.search(EXIT) !== -1) {
Logger.info(TAG, `EXIT ${EXIT}`)
context.terminateSelf((error) = > {
Logger.error(TAG, `terminateSelf finished, error= ${error}`)
})
} else {
if (value === 'null') {
this.expression = ''
} else {
this.expression = value
}
if (this.isOperator(this.expression.substr(this.expression.length - 1, this.expression.length))) {
this.result = calc(this.expression.substring(0, this.expression.length - 1))
} else {
this.result = calc(this.expression)
}
}
}
})
}
startAbilityCallBack = (key: string) = > {
Logger.info(TAG, `startAbilityCallBack ${key}`)
if (DATA_CHANGE === key) {
this.kvStoreModel.put(DATA_CHANGE, this.expression)
}
if (EXIT === key) {
this.kvStoreModel.put(DATA_CHANGE, EXIT)
}
}
build() {
Column() {
TitleBarComponent({
isLand: this.isLand,
startAbilityCallBack: this.startAbilityCallBack,
remoteDeviceModel: this.remoteDeviceModel,
isDistributed: $isDistributed,
isShow: this.isShow
})
if (this.isLand) {
Row() {
InputComponent({ isLand: this.isLand, result: $result, expression: $expression })
ButtonComponentHorizontal({ onInputValue: this.onInputValue })
}
.width('100%')
.layoutWeight(1)
} else {
Column() {
InputComponent({ isLand: this.isLand, result: $result, expression: $expression })
ButtonComponent({ onInputValue: this.onInputValue })
}
.width('100%')
}
}
.width('100%')
.height('100%')
}
}
- 当用户点击“X”按钮后,删除运算表达式的最后一个字符。
- 当用户点击“=”按钮后,将调用calc(this.expression)对表达式进行数据计算。