分布式計算器
介紹
本示例使用分布式能力實現了一個簡單的計算器應用,可以進行簡單的數值計算,支持遠程拉起另一個設備的計算器應用,兩個計算器應用進行協同計算。
遠程拉起:通過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)對表達式進行數據計算。
審核編輯 黃宇
-
分布式
+關注
關注
1文章
899瀏覽量
74508 -
OpenHarmony
+關注
關注
25文章
3722瀏覽量
16317
發布評論請先 登錄
相關推薦
評論