提交 e99e57ee 编写于 作者: W wangqingkaihong

资料提交

Signed-off-by: Nwangqingkaihong <wangqing@kaihong.com>
上级 80ea25d2
# 分布式文件场景
## 场景说明
两台设备组网,实现修改文件时两个设备同时修改的分布式文件场景有助于加快工作效率,减少工作中的冗余,本例将为大家介绍如何实现上述功能。
## 效果呈现
本例效果如下:
![contactlist](figures/distributed.png)
## 运行环境
- IDE:DevEco Studio 4.0.0.201 Beta1
- SDK:Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1)
## 实现思路
在分布式文件场景中,分布式设备管理包含了分布式设备搜索、分布式设备列表弹窗、远端设备拉起三部分。
首先在分布式组网内搜索设备,然后把设备展示到分布式设备列表弹窗中,最后根据用户的选择拉起远端设备。
- 分布式设备搜索:通过SUBSCRIBE_ID搜索分布式组网内的设备。
- 分布式设备列表弹窗:使用@CustomDialog装饰器来装饰分布式设备列表弹窗。
- 远端设备拉起:通过startAbility(deviceId)方法拉起远端设备的包。
- 分布式数据管理:(1)管理分布式数据库:创建一个distributedObject分布式数据对象实例,用于管理分布式数据对象。
​ (2)订阅分布式数据变化:通过this.distributedObject.on('status', this.statusCallback)监听分布式数据对象的变更。
## 开发步骤
1.申请所需权限
​ 在model.json5中添加以下配置:
```json
"requestPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"//允许不同设备间的数据交换
},
{
"name": "ohos.permission.ACCESS_SERVICE_DM"//允许系统应用获取分布式设备的认证组网能力
}
]
```
2.构建UI框架
index页面:
```typescript
build() {
Column() {
TitleBar({ rightBtn: $r('app.media.trans'), onRightBtnClicked: this.showDialog })
//自/common/TitleBar.ets中引入标题栏相关。点击标题栏中的右侧按钮会调用showDialog()函数连接组网设备
Row() {
Text($r('app.string.state'))
.fontSize(30)
Image(this.isOnline ? $r('app.media.green') : $r('app.media.red'))//两台设备组网成功后状态显示为绿色、否则为红色
.size({ width: 30, height: 30 })
.objectFit(ImageFit.Contain)
}
.width('100%')
.padding(16)
//通过数据懒加载的方式从数据源中每次迭代一个文件进行展示,可用列表被放置在滚动容器中,被划出可视区域外的资源会被回收
List({ space: 10 }) {
LazyForEach(this.noteDataSource, (item: Note, index) => {
ListItem() {
NoteItem({ note: item, index: index })//NoteItem引入自common/NoteItem.ets,负责主页文件信息的呈现
.id(`${item.title}`)
}
}, item => JSON.stringify(item))
}
.width('95%')
.margin(10)
.layoutWeight(1)
Row() {
Column() {
Image($r('app.media.clear'))//清除按钮
.size({ width: 40, height: 40 })
Text($r('app.string.clear'))
.fontColor(Color.Red)
.fontSize(20)
}.layoutWeight(1)
.id('clearNote')
.onClick(() => {
//点击清除按钮清除所有文件
Logger.info(TAG, 'clear notes')
this.noteDataSource['dataArray'] = []
this.noteDataSource.notifyDataReload()
this.globalObject.clear()
AppStorage.SetOrCreate('sessionId', this.sessionId)
})
Column() {
Image($r('app.media.add'))//添加按钮
.size({ width: 40, height: 40 })
Text($r('app.string.add'))
.fontColor(Color.Black)
.fontSize(20)
}.layoutWeight(1)
.id('addNote')
.onClick(() => {
//点击添加按钮跳转到编辑页面
router.push({
url: 'pages/Edit',
params: {
note: new Note('', '', -1),
isAdd: true
}
})
})
}
.width('100%')
.padding(10)
.backgroundColor('#F0F0F0')
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
....................................
//common/NoteItem.ets
import router from '@ohos.router'
import { MARKS } from '../model/Const'
import Note from '../model/Note'
@Component
export default struct NoteItem {
@State note: Note | undefined = undefined
private index: number = 0
build() {
Row() {
Image(this.note.mark >= 0 ? MARKS[this.note.mark] : $r('app.media.note'))//文件标志图片
.size({ width: 30, height: 30 })
.objectFit(ImageFit.Contain)
Column() {
Text(this.note.title)//文件标题
.fontColor(Color.Black)
.fontSize(30)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.note.content)//文件内容
.fontColor(Color.Gray)
.margin({ top: 10 })
.fontSize(25)
.maxLines(1)//在列表中最多展示一行
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 20 })
}
.padding(16)
.width('100%')
.borderRadius(16)
.backgroundColor(Color.White)
.onClick(() => {
//点击文件进入此文件编辑页面
router.push({
url: 'pages/Edit',
params: {
index: this.index,
note: this.note,
isAdd: false
}
})
})
}
}
```
Edit页面:
```typescript
build() {
Column() {
TitleBar({ title: this.note.title === '' ? $r('app.string.add_note') : this.note.title })
Column() {
Row() {
Image(this.note.mark >= 0 ? MARKS[this.note.mark] : $r('app.media.mark'))
.width(30)
.aspectRatio(1)
.margin({ left: 16, top: 16 })
.objectFit(ImageFit.Contain)
.alignSelf(ItemAlign.Start)
Select([{ value: ' ', icon: MARKS[0] },
{ value: ' ', icon: MARKS[1] },
{ value: ' ', icon: MARKS[2] },
{ value: ' ', icon: MARKS[3] },
{ value: ' ', icon: MARKS[4] }])
.selected(this.note.mark)
.margin({ top: 5 })
.onSelect((index: number) => {
this.note.mark = index
})
}
.width('100%')
TextInput({ placeholder: 'input the title', text: this.note.title })//文件标题输入框
.id('titleInput')
.placeholderColor(Color.Gray)
.fontSize(30)
.margin({ left: 15, right: 15, top: 15 })
.height(60)
.backgroundColor(Color.White)
.onChange((value: string) => {
this.note.title = value
})
TextArea({ placeholder: 'input the content', text: this.note.content })//文件内容输入区域
.id('contentInput')
.placeholderColor(Color.Gray)
.backgroundColor(Color.White)
.fontSize(30)
.height('35%')
.margin({ left: 16, right: 16, top: 16 })
.textAlign(TextAlign.Start)
.onChange((value: string) => {
this.note.content = value
})
Button() {
//保存按钮
Text($r('app.string.save'))
.fontColor(Color.White)
.fontSize(17)
}
.id('saveNote')
.backgroundColor('#0D9FFB')
.height(50)
.width(200)
.margin({ top: 20 })
.onClick(() => {
//点击按钮时调用model/DistributedObjectModel.ts定义的类globalObject中的方法
if (!this.isAdd) {
let index = router.getParams()['index']
this.globalObject.update(index, this.note.title, this.note.content, this.note.mark)//编辑时更新内容
} else {
this.globalObject.add(this.note.title, this.note.content, this.note.mark)//新建时添加内容
}
router.back()//返回主页
})
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
```
3.将两台设备组网
```typescript
showDialog = () => {
//RemoteDeviceModel引入自model/RemoteDeviceModel.ts
RemoteDeviceModel.registerDeviceListCallback(() => {
//得到附近可信的设备列表
Logger.info(TAG, 'registerDeviceListCallback, callback entered')
this.devices = []
this.devices = RemoteDeviceModel.discoverDevices.length > 0 ? RemoteDeviceModel.discoverDevices : RemoteDeviceModel.devices
if (this.dialogController) {
this.dialogController.close()
this.dialogController = undefined
}
this.dialogController = new CustomDialogController({
builder: DeviceDialog({
devices: this.devices,
onSelectedIndexChange: this.onSelectedDevice
}),
autoCancel: true
})
this.dialogController.open()
})
}
....................................
//model/RemoteDeviceModel.ts
import deviceManager from '@ohos.distributedHardware.deviceManager'
registerDeviceListCallback(stateChangeCallback: () => void) {
if (typeof (this.deviceManager) !== 'undefined') {
this.registerDeviceListCallbackImplement(stateChangeCallback)
return
}
Logger.info(TAG, 'deviceManager.createDeviceManager begin')
try {
deviceManager.createDeviceManager(BUNDLE, (error, value) => {
if (error) {
Logger.error(TAG, 'createDeviceManager failed.')
return
}
this.deviceManager = value
this.registerDeviceListCallbackImplement(stateChangeCallback)
Logger.info(TAG, `createDeviceManager callback returned,value=${value}`)
})
} catch (error) {
Logger.error(TAG, `createDeviceManager throw error, code=${error.code} message=${error.message}`)
}
Logger.info(TAG, 'deviceManager.createDeviceManager end')
}
registerDeviceListCallbackImplement(stateChangeCallback: () => void) {
Logger.info(TAG, 'registerDeviceListCallback')
this.stateChangeCallback = stateChangeCallback
if (this.deviceManager === undefined) {
Logger.error(TAG, 'deviceManager has not initialized')
this.stateChangeCallback()
return
}
Logger.info(TAG, 'getTrustedDeviceListSync begin')
try {
let list = this.deviceManager.getTrustedDeviceListSync()//同步获取所有可信设备列表
Logger.info(TAG, `getTrustedDeviceListSync end, devices=${JSON.stringify(list)}`)
if (typeof (list) !== 'undefined' && typeof (list.length) !== 'undefined') {
this.devices = list
}
} catch (error) {
Logger.error(TAG, `getLocalDeviceInfoSync throw error, code=${error.code} message=${error.message}`)
}
this.stateChangeCallback()
Logger.info(TAG, 'callback finished')
try {
this.deviceManager.on('deviceStateChange', (data) => {
if (data === null) {
return
}
Logger.info(TAG, `deviceStateChange data = ${JSON.stringify(data)}`)
switch (data.action) {
case deviceManager.DeviceStateChangeAction.READY://即设备处于可用状态,表示设备间信息已在分布式数据中同步完成, 可以运行分布式业务
this.discoverDevices = []
this.devices.push(data.device)
this.stateChangeCallback()
try {
let list = this.deviceManager.getTrustedDeviceListSync()
if (typeof (list) !== 'undefined' && typeof (list.length) !== 'undefined') {
this.devices = list
}
} catch (error) {
Logger.error(TAG, `getTrustedDeviceListSync throw error, code=${error.code} message=${error.message}`)
}
this.stateChangeCallback()
break
default:
break
}
})
this.deviceManager.on('deviceFound', (data) => {
if (data === null) {
return
}
Logger.info(TAG, `deviceFound data=${JSON.stringify(data)}`)
this.onDeviceFound(data)
})
this.deviceManager.on('discoverFail', (data) => {
Logger.info(TAG, `discoverFail data=${JSON.stringify(data)}`)
})
this.deviceManager.on('serviceDie', () => {
Logger.info(TAG, 'serviceDie')
})
} catch (error) {
Logger.error(TAG, `on throw error, code=${error.code} message=${error.message}`)
}
this.startDeviceDiscovery()
}
startDeviceDiscovery() {
SUBSCRIBE_ID = Math.floor(65536 * Math.random())
var info = {
subscribeId: SUBSCRIBE_ID,
mode: 0xAA,
medium: 2,
freq: 2,//高频率
isSameAccount: false,
isWakeRemote: true,
capability: 0
}
Logger.info(TAG, `startDeviceDiscovery${SUBSCRIBE_ID}`)
try {
this.deviceManager.startDeviceDiscovery(info)//开始发现周边设备
} catch (error) {
Logger.error(TAG, `startDeviceDiscovery throw error, code=${error.code} message=${error.message}`)
}
}
```
4.实现同步编辑
```typescript
onPageShow() {
//每当完成编辑或者新建文件,就会回到主页,此时就会执行onPageShow()
this.noteDataSource['dataArray'] = this.globalObject.distributedObject.documents
this.noteDataSource.notifyDataReload()
Logger.info(TAG, `this.sessionId = ${this.sessionId}`)
Logger.info(TAG, `globalSessionId = ${this.globalSessionId}`)
if (this.sessionId !== this.globalSessionId) {
this.sessionId = this.globalSessionId
this.share()
}
}
share() {
//多个设备间的对象如果设置为同一个sessionId的笔记数据自动同步
Logger.info(TAG, `sessionId = ${this.sessionId}`)
this.globalObject.setChangeCallback(() => {
this.noteDataSource['dataArray'] = this.globalObject.distributedObject.documents
this.noteDataSource.notifyDataReload()
})
this.globalObject.setStatusCallback((session, networkId, status) => {
Logger.info(TAG, `StatusCallback,${status}`)
if (status === 'online') {
this.isOnline = true
} else {
this.isOnline = false
}
})
this.globalObject.distributedObject.setSessionId(this.sessionId)
AppStorage.SetOrCreate('objectModel', this.globalObject)
}
```
## 全部代码
本例完整代码sample示例链接:[分布式文件](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SuperFeature/DistributedAppDev/DistributedNote)
## 参考
[权限列表](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md#ohospermissiondistributed_datasync)
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
```json ```json
"requestPermissions": [ "requestPermissions": [
{ {
//允许应用查看蓝牙的配置 //允许应用查看蓝牙的配置
"name": "ohos.permission.USE_BLUETOOTH", "name": "ohos.permission.USE_BLUETOOTH",
"reason": "$string:grant_use_bluetooth", "reason": "$string:grant_use_bluetooth",
"usedScene": { "usedScene": {
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
} }
}, },
{ {
//允许应用配置本地蓝牙,查找远端设备且与之配对连接 //允许应用配置本地蓝牙,查找远端设备且与之配对连接
"name": "ohos.permission.DISCOVER_BLUETOOTH", "name": "ohos.permission.DISCOVER_BLUETOOTH",
"reason": "$string:grant_discovery_bluetooth", "reason": "$string:grant_discovery_bluetooth",
"usedScene": { "usedScene": {
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
} }
}, },
{ {
//允许应用获取设备位置信息 //允许应用获取设备位置信息
"name": "ohos.permission.LOCATION", "name": "ohos.permission.LOCATION",
"reason": "$string:grant_location", "reason": "$string:grant_location",
"usedScene": { "usedScene": {
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
} }
}, },
{ {
//允许应用获取设备模糊位置信息 //允许应用获取设备模糊位置信息
"name": "ohos.permission.APPROXIMATELY_LOCATION", "name": "ohos.permission.APPROXIMATELY_LOCATION",
"reason": "$string:grant_location", "reason": "$string:grant_location",
"usedScene": { "usedScene": {
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
} }
}, },
{ {
//允许应用配对蓝牙设备,并对设备的电话簿或消息进行访问 //允许应用配对蓝牙设备,并对设备的电话簿或消息进行访问
"name": "ohos.permission.MANAGE_BLUETOOTH", "name": "ohos.permission.MANAGE_BLUETOOTH",
"reason": "$string:grant_manage_bluetooth", "reason": "$string:grant_manage_bluetooth",
"usedScene": { "usedScene": {
...@@ -90,7 +90,7 @@ ...@@ -90,7 +90,7 @@
2. 构建UI框架,整体的UI框架分为TitleBar(页面名称),PinDialog(配对蓝牙弹框),index(主页面)三个部分。 2. 构建UI框架,整体的UI框架分为TitleBar(页面名称),PinDialog(配对蓝牙弹框),index(主页面)三个部分。
```ts ```ts
//Common/TitleBar.ets //Common/TitleBar.ets
@Component @Component
export struct TitleBar { export struct TitleBar {
private handlerClickButton: () => void private handlerClickButton: () => void
...@@ -118,14 +118,14 @@ ...@@ -118,14 +118,14 @@
} }
} }
//Common/PinDalog.ets //Common/PinDalog.ets
... ...
aboutToAppear() { aboutToAppear() {
this.titleText = `"${this.data.deviceId}"要与您配对。请确认此配对码已在"${this.data.deviceId}"上直接显示,且不是手动输入的。` this.titleText = `"${this.data.deviceId}"要与您配对。请确认此配对码已在"${this.data.deviceId}"上直接显示,且不是手动输入的。`
this.pinCode = JSON.stringify(this.data.pinCode) this.pinCode = JSON.stringify(this.data.pinCode)
} }
build() { build() {
//配对弹框描述文字 //配对弹框描述文字
Column({ space: 10 }) { Column({ space: 10 }) {
Text($r('app.string.match_request')) Text($r('app.string.match_request'))
.fontSize(30) .fontSize(30)
...@@ -152,7 +152,7 @@ ...@@ -152,7 +152,7 @@
.margin({ top: 5 }) .margin({ top: 5 })
Row() { Row() {
//配对选择UI,取消or配对 //配对选择UI,取消or配对
this.choiceText($r('app.string.cancel'), () => { this.choiceText($r('app.string.cancel'), () => {
bluetooth.setDevicePairingConfirmation(this.data.deviceId, false) bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)
logger.info(TAG, `setDevicePairingConfirmation = ${bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)}`) logger.info(TAG, `setDevicePairingConfirmation = ${bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)}`)
...@@ -186,7 +186,7 @@ ...@@ -186,7 +186,7 @@
Scroll() { Scroll() {
Column() { Column() {
Row() { Row() {
//蓝牙开关 //蓝牙开关
Column() { Column() {
Text($r('app.string.bluetooth')) Text($r('app.string.bluetooth'))
.fontSize(30) .fontSize(30)
...@@ -227,7 +227,7 @@ ...@@ -227,7 +227,7 @@
.color('#ffece7e7') .color('#ffece7e7')
.lineCap(LineCapStyle.Butt) .lineCap(LineCapStyle.Butt)
.margin('1%') .margin('1%')
//已配对的设备 //已配对的设备
Text($r('app.string.paired_device')) Text($r('app.string.paired_device'))
.fontSize(25) .fontSize(25)
.fontColor('#ff565555') .fontColor('#ff565555')
...@@ -246,7 +246,7 @@ ...@@ -246,7 +246,7 @@
.id(`pairedDevice${index}`) .id(`pairedDevice${index}`)
.onClick(() => { .onClick(() => {
AlertDialog.show({ AlertDialog.show({
//取消配对 //取消配对
title: $r('app.string.disconnect'), title: $r('app.string.disconnect'),
message: '此操作将会断开您与以下设备的连接:' + item, message: '此操作将会断开您与以下设备的连接:' + item,
primaryButton: { primaryButton: {
...@@ -254,7 +254,7 @@ ...@@ -254,7 +254,7 @@
action: () => { action: () => {
} }
}, },
//确认取消 //确认取消
secondaryButton: { secondaryButton: {
value: $r('app.string.confirm'), value: $r('app.string.confirm'),
action: () => { action: () => {
...@@ -284,7 +284,7 @@ ...@@ -284,7 +284,7 @@
.fontColor('#ff565555') .fontColor('#ff565555')
.margin({ left: '5%', bottom: '2%' }) .margin({ left: '5%', bottom: '2%' })
.alignSelf(ItemAlign.Start) .alignSelf(ItemAlign.Start)
//可用设备列表 //可用设备列表
ForEach(this.discoveryList, (item) => { ForEach(this.discoveryList, (item) => {
Row() { Row() {
Text(item) Text(item)
...@@ -294,7 +294,7 @@ ...@@ -294,7 +294,7 @@
.width('100%') .width('100%')
.height(50) .height(50)
.margin({ left: '5%', top: '1%' }) .margin({ left: '5%', top: '1%' })
//进行配对操作点击 //进行配对操作点击
.onClick(() => { .onClick(() => {
logger.info(TAG, `start bluetooth.pairDevice,item = ${item}`) logger.info(TAG, `start bluetooth.pairDevice,item = ${item}`)
let pairStatus = bluetooth.pairDevice(item) let pairStatus = bluetooth.pairDevice(item)
...@@ -320,10 +320,10 @@ ...@@ -320,10 +320,10 @@
initBluetooth() { initBluetooth() {
bluetooth.on('stateChange', (data) => { bluetooth.on('stateChange', (data) => {
logger.info(TAG, `enter on stateChange`) logger.info(TAG, `enter on stateChange`)
//判断蓝牙开关状态 //判断蓝牙开关状态
if (data === bluetooth.BluetoothState.STATE_ON) { if (data === bluetooth.BluetoothState.STATE_ON) {
logger.info(TAG, `enter BluetoothState.STATE_ON`) logger.info(TAG, `enter BluetoothState.STATE_ON`)
//蓝牙打开后的相关操作 //蓝牙打开后的相关操作
this.foundDevices() this.foundDevices()
} }
if (data === bluetooth.BluetoothState.STATE_OFF) { if (data === bluetooth.BluetoothState.STATE_OFF) {
...@@ -331,20 +331,20 @@ ...@@ -331,20 +331,20 @@
bluetooth.off('bluetoothDeviceFind', (data) => { bluetooth.off('bluetoothDeviceFind', (data) => {
logger.info(TAG, `offBluetoothDeviceFindData = ${JSON.stringify(data)}`) logger.info(TAG, `offBluetoothDeviceFindData = ${JSON.stringify(data)}`)
}) })
//关闭 //关闭
bluetooth.stopBluetoothDiscovery() bluetooth.stopBluetoothDiscovery()
this.discoveryList = [] this.discoveryList = []
} }
logger.info(TAG, `BluetoothState = ${JSON.stringify(data)}`) logger.info(TAG, `BluetoothState = ${JSON.stringify(data)}`)
}) })
//开启蓝牙 //开启蓝牙
bluetooth.enableBluetooth() bluetooth.enableBluetooth()
} }
``` ```
4. 设置蓝牙扫描模式并开启扫描去发现设备,并订阅蓝牙设备发现上报时间获取设备列表 4. 设置蓝牙扫描模式并开启扫描去发现设备,并订阅蓝牙设备发现上报时间获取设备列表
```ts ```ts
foundDevices() { foundDevices() {
//订阅蓝牙设备发现上报事件 //订阅蓝牙设备发现上报事件
bluetooth.on('bluetoothDeviceFind', (data) => { bluetooth.on('bluetoothDeviceFind', (data) => {
logger.info(TAG, `enter on bluetoothDeviceFind`) logger.info(TAG, `enter on bluetoothDeviceFind`)
if (data !== null && data.length > 0) { if (data !== null && data.length > 0) {
...@@ -359,9 +359,9 @@ ...@@ -359,9 +359,9 @@
logger.info(TAG, `deviceList = ${JSON.stringify(this.deviceList)}`) logger.info(TAG, `deviceList = ${JSON.stringify(this.deviceList)}`)
} }
}) })
//开启蓝牙扫描,可以发现远端设备 //开启蓝牙扫描,可以发现远端设备
bluetooth.startBluetoothDiscovery() bluetooth.startBluetoothDiscovery()
//设置蓝牙扫描模式,可以被远端设备发现 //设置蓝牙扫描模式,可以被远端设备发现
bluetooth.setBluetoothScanMode(bluetooth.ScanMode.SCAN_MODE_CONNECTABLE_GENERAL_DISCOVERABLE, TIME) bluetooth.setBluetoothScanMode(bluetooth.ScanMode.SCAN_MODE_CONNECTABLE_GENERAL_DISCOVERABLE, TIME)
} }
``` ```
...@@ -369,12 +369,12 @@ ...@@ -369,12 +369,12 @@
5. 设置蓝牙扫描模式并开启扫描去发现设备,并订阅蓝牙设备发现上报时间获取设备列表 5. 设置蓝牙扫描模式并开启扫描去发现设备,并订阅蓝牙设备发现上报时间获取设备列表
```ts ```ts
//配对确定和取消代码在PinDialog.ets文件中 //配对确定和取消代码在PinDialog.ets文件中
//setDevicePairingConfirmation(device: string, accept: boolean): void //setDevicePairingConfirmation(device: string, accept: boolean): void
//device string 表示远端设备地址,例如:"XX:XX:XX:XX:XX:XX"。 //device string 表示远端设备地址,例如:"XX:XX:XX:XX:XX:XX
//accept boolean 接受配对请求设置为true,否则设置为false //accept boolean 接受配对请求设置为true,否则设置为false
//订阅蓝牙配对状态改变事件,根据蓝牙状态更新设备列表 //订阅蓝牙配对状态改变事件,根据蓝牙状态更新设备列表
bluetooth.on('bondStateChange', (data) => { bluetooth.on('bondStateChange', (data) => {
logger.info(TAG, `enter bondStateChange`) logger.info(TAG, `enter bondStateChange`)
logger.info(TAG, `data = ${JSON.stringify(data)}`) logger.info(TAG, `data = ${JSON.stringify(data)}`)
......
# 如何调用设备摄像头进行拍照、预览并将拍摄结果保存在媒体库中
## 场景说明
调用设备摄像头进行拍照、预览是许多应用开发过程中都需要的功能。在拍照完成时显示照片预览图可以确认拍摄的照片是否达到预期,本例将为大家介绍如何实现上述功能。
## 效果呈现
本例效果如下:
![contactlist](figures/camera.png)
## 运行环境
- IDE:DevEco Studio 4.0.0.201 Beta1
- SDK:Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1)
## 实现思路
本例使用@ohos.multimedia.camera接口实现相机示例的主要功能:拍照、预览;
使用@ohos.multimedia.image接口实现图片接收;
使用@ohos.multimedia.mediaLibrary接口实现对媒体文件的存储。
- XComponent组件负责绘制摄像头画面呈现的窗口,其onload事件调用cameraModel.ts的initCamera方法初始化相机功能输出预览信息。
- 拍照按钮:使用Image组件实现,其onclick事件调用cameraModel.ts的takepicture方法开始拍照。
- 照片存储:MediaModel.ts中的createAndGetUri方法通过引用自@ohos.multimedia.mediaLibrary的mediaLibraryTest类创建媒体资源,saveImage方法将拍摄的照片写入到Mediamodel传回的资源中去。
## 开发步骤
1.申请所需权限
​ 在model.json5中添加以下配置:
```json
"requestPermissions": [
{
"name": "ohos.permission.CAMERA"//允许应用使用相机拍摄照片和录制视频
},
{
"name": "ohos.permission.MICROPHONE"//允许应用使用麦克风
},
{
"name": "ohos.permission.MEDIA_LOCATION"//允许应用访问用户媒体文件中的地理位置信息
},
{
"name": "ohos.permission.WRITE_MEDIA"//允许应用读写用户外部存储中的媒体文件信息
},
{
"name": "ohos.permission.READ_MEDIA"//允许应用读取用户外部存储中的媒体文件信息
}
]
```
2.创建绘制组件XComponent
```typescript
build() {
Column() {
Title()
.visibility(this.isTitleShow ? Visibility.Visible : Visibility.None)
Stack({ alignContent: Alignment.Bottom }) {
Stack({ alignContent: Alignment.TopStart }) {
XComponent({
id: 'componentId',
type: 'surface',
controller: this.mXComponentController //将控制器绑定至XComponent组件
})
.onLoad(() => {
this.mXComponentController.setXComponentSurfaceSize({ surfaceWidth: 640, surfaceHeight: 480 });//设置surface大小
this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
this.currentModel = CameraMode.modePhoto;
this.cameraModel.initCamera(this.surfaceId); //调用model/cameraModel.ts初始化相机功能
})
.width('100%')
.height('100%')
.margin({ bottom: 152 })
Column() {
}
.width('97%')
.height('100%')
```
3.初始化相机功能
```typescript
import image from '@ohos.multimedia.image';//自@ohos.multimedia.image引入image,提供图片处理效果
..................
private receiver: image.ImageReceiver = undefined;//图像接收类,用于获取组件surface id,接收最新的图片和读取下一张图片
..................
constructor() {
this.mediaModel = MediaModel.getMediaInstance();//通过调用model/MediaModel.ets中的方法创建mediaInstance类mediaModel
//创建ImageReceiver实例receiver
this.receiver = image.createImageReceiver(
cameraWH.width,
cameraWH.height,
FOUR,
EIGHT
);
//接收图片时注册回调
this.receiver.on('imageArrival', () => {
//从ImageReceiver读取下一张图片
this.receiver.readNextImage((err, image) => {
if (err || image === undefined) {
return;
}
//根据图像的组件类型从图像中获取组件缓存
image.getComponent(FOUR, (errMsg, img) => {
if (errMsg || img === undefined) {
return;
}
let buffer = new ArrayBuffer(FOUR_THOUSAND_AND_SIXTY_NINE);
if (img.byteBuffer) {
buffer = img.byteBuffer;
}
this.saveImage(buffer, image);
});
});
});
}
async initCamera(surfaceId: string): Promise<void> {
..................
try {
this.cameraMgr = camera.getCameraManager(globalThis.cameraContext);//获取相机管理器实例
}
this.camerasArray = this.cameraMgr.getSupportedCameras();//获取支持指定的相机设备对象
if (this.camerasArray.length === 0) {
return;
}
let mCamera = this.camerasArray[0];
this.cameraInput = this.cameraMgr.createCameraInput(mCamera);
this.cameraInput.open();
this.capability = this.cameraMgr.getSupportedOutputCapability(mCamera);//查询相机设备支持的输出能力
let previewProfile = this.capability.previewProfiles[0];
//通过相机管理器创建预览输出对象
this.previewOutput = this.cameraMgr.createPreviewOutput(
previewProfile,
surfaceId //surfaceId从XComponent组件获取
);
let rSurfaceId = await this.receiver.getReceivingSurfaceId();//获取一个surface id供其他组件使用
let photoProfile = this.capability.photoProfiles[0];
//通过相机管理器创建照片输出对象
this.photoOutPut = this.cameraMgr.createPhotoOutput(
photoProfile,
rSurfaceId //rSurfaceId通过构造函数中定义的图像接收类receiver获取
);
this.capSession = this.cameraMgr.createCaptureSession();//创建CaptureSession实例
this.capSession.beginConfig();//开始配置会话
this.capSession.addInput(this.cameraInput);//将cameraInput加入会话
this.capSession.addOutput(this.previewOutput);//将预览输出加入会话
this.capSession.addOutput(this.photoOutPut);//将照片输出加入会话
await this.capSession.commitConfig();//提交配置信息
await this.capSession.start();//开始输出
}
```
4.点击按钮进行拍照
```typescript
Image(this.getCameraIcon())
.size({ width: 64, height: 64 })
.margin({ left: 10 })
.id('camera')
.onClick(() => {
if (this.currentModel === CameraMode.modePhoto) {
prompt.showToast({ message: '拍照中...', duration: 200 });
this.cameraModel.takePicture();//调用model/cameraModel.takePicture()开始拍照
}
})
```
5.拍照功能具体实现
- 拍照
```typescript
async takePicture(): Promise<void> {
//设置拍照相关参数
let photoSettings = {
rotation: this.imageRotation,
quality: camera.QualityLevel.QUALITY_LEVEL_MEDIUM,
location: {
// 位置信息,经纬度
latitude: 12.9698,
longitude: 77.75,
altitude: 1000,
},
mirror: false,
};
await this.photoOutPut.capture(photoSettings);
AppStorage.Set('isRefresh', true);
}
```
- 保存图片
```typescript
..................//model/MediaModel.ts中定义的负责保存图片的相关方法
async createAndGetUri(mediaType: mediaLibrary.MediaType): Promise<mediaLibrary.FileAsset> {
let dateTimeUtil: DateTimeUtil = new DateTimeUtil();
let info: FileInfo = this.getInfoFromMediaType(mediaType);
let name: string = `${dateTimeUtil.getDate()}_${dateTimeUtil.getTime()}`;//获取当前时间
let displayName: string = `${info.prefix}${name}${info.suffix}`;
//获取公共目录路径。
let publicPath: string = await this.mediaLibraryTest.getPublicDirectory(
info.directory
);//通过引用自@ohos.multimedia.mediaLibrary的mediaLibraryTest类创建媒体资源,其中定义了媒体类型、名称、路径。
let fileAsset: mediaLibrary.FileAsset = await this.mediaLibraryTest.createAsset(
mediaType,//根据传入函数createAndGetUri的mediaType参数决定创建什么类型的媒体资源
displayName,
publicPath
);
return fileAsset;
}
async getFdPath(fileAsset: mediaLibrary.FileAsset): Promise<number> {
let fd: number = await fileAsset.open('Rw');//打开当前文件
return fd;
}
..................
async saveImage(buffer: ArrayBuffer, img: image.Image): Promise<void> {
this.fileAsset = await this.mediaModel.createAndGetUri(mediaLibrary.MediaType.IMAGE);
//通过调用MediaModel中的方法创建Image类型资源
this.photoPath = this.fileAsset.uri;
this.fd = await this.mediaModel.getFdPath(this.fileAsset);
await fileIo.write(this.fd, buffer);//将拍摄的照片写入到Mediamodel传回的资源中去
await this.fileAsset.close(this.fd);//释放open函数
await img.release();
if (this.takePictureHandle) {
this.takePictureHandle(this.photoPath);
}
}
```
## 全部代码
本例完整代码sample示例链接:[相机](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/Media/Camera)
## 参考
[权限列表](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md#ohospermissiondistributed_datasync)
# 如何实现WLAN网络扫描、连接、断开
## 场景说明
对可用的WLAN列表进行扫描,连接、断开WLAN网络是设备常见的功能,本例将为大家介绍如何实现上述功能。
## 效果呈现
本例效果如下:
| 连接WIFI | WIFI列表 |
| :---------------------------------: | :----------------------------------: |
| ![contactlist](figures/connect.png) | ![contactlist](figures/wlanmain.png) |
## 运行环境
- IDE:DevEco Studio 4.0.0.201 Beta1
- SDK:Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1)
## 实现思路
本例主要通过@ohos.wifiManager 相关API实现wlan激活和关闭、扫描和连接WIFI等功能。
- WLAN功能的激活和关闭:点击首页的切换按钮,如果是打开,使用wifi.enableWifi()开启wifi;如果是关闭,则使用wifi.disconnect()断开wifi, 然后使用wifi.disableWifi()关闭wifi
- WIFI的连接:点击wifi列表中加密的wifi,并在弹窗中输入密码后,会在AvailableWifi.ets中通过WifiModule.connectNetwork()调用wifi.connectToDevice()连接wifi
- WIFI的扫描:进入Index.ets 后就会间歇性的调用wifi.scan()开启扫描,然后通过WifiModel模块中的getScanInfos()调用wifi.getScanResults()来获取扫描的结果
## 开发步骤
1.申请所需权限
​ 在model.json5中添加以下配置:
```json
"requestPermissions": [
{
"name": "ohos.permission.GET_WIFI_INFO"//允许应用获取WLAN信息
},
{
"name": "ohos.permission.GET_WIFI_INFO_INTERNAL"//允许应用获取WLAN信息
},
{
"name": "ohos.permission.SET_WIFI_INFO"//允许应用配置WLAN设备
},
{
"name": "ohos.permission.GET_WIFI_PEERS_MAC"//允许应用获取对端WLAN或者蓝牙设备的MAC地址
},
{
"name": "ohos.permission.GET_WIFI_LOCAL_MAC"//允许应用获取本机WLAN或者蓝牙设备的MAC地址
},
{
"name": "ohos.permission.GET_WIFI_CONFIG"//允许应用获取WLAN配置信息
},
{
"name": "ohos.permission.SET_WIFI_CONFIG"//允许应用配置WLAN信息
},
{
"name": "ohos.permission.MANAGE_WIFI_CONNECTION"//允许应用管理WLAN连接
},
{
"name": "ohos.permission.MANAGE_WIFI_HOTSPOT"//允许应用开启或者关闭WLAN热点
},
{
"name": "ohos.permission.LOCATION"//允许应用获取设备位置信息
},
{
"name": "ohos.permission.APPROXIMATELY_LOCATION"//允许应用获取设备模糊位置信息
}
]
```
2.准备工作
```typescript
import wifi from '@ohos.wifiManager'
aboutToAppear() {
// 如果wifi是开的,就记录下状态,然后扫描wifi,并获取连接信息
if (wifi.isWifiActive()) {//查询wifi是否已使能
Logger.log(TAG, 'wifi is active')
this.isSwitchOn = true//已使能说明wifi开关已被打开,那么让isSwitchOn为true
wifi.scan()//@ohos.wifiManager的接口,对wifi进行扫描
this.scan()//自定义的scan函数,确保扫描的持续性
this.getLinkedInfo()
}
// 启动监听
this.addListener()//监听连接状态的变化
}
```
3.持续地扫描可用wifi
```typescript
async getLinkedInfo() {
try {
//调用getLinkedInfo接口得到是否已连接wifi的信息
let wifiLinkedInfo = await wifi.getLinkedInfo()
if (wifiLinkedInfo === null || wifiLinkedInfo.bssid === '') {
//如果结果为空,则说明未连接
this.isLinked = false
this.linkedInfo = null
return
}
this.isLinked = true
this.linkedInfo = wifiLinkedInfo
} catch (err) {
Logger.info(`getLinkedInfo failed err is ${JSON.stringify(err)}`)
}
}
async scan() {
// 获取有关Wi-Fi连接的信息,存入linkedInfo
await this.getLinkedInfo()
// 不停地扫描wifi
let result: Array<WifiType> = await this.wifiModel.getScanInfos()//调用model/WifiModel.ets中的getScanInfos得到扫描结果
if (this.isSwitchOn) {
//如果wifi功能已打开,则要进行不停地扫描
AppStorage.SetOrCreate('wifiList', result)//将扫描结果存至全局
//每3000毫秒调用一次scan(),实现不停扫描可用wifi
setTimeout(async () => {
await this.scan()
}, 3000)
}
}
addListener() {
// wifi连接状态改变事件发生时,修改连接信息
wifi.on('wifiConnectionChange', async state => {
Logger.log(TAG, `wifiConnectionChange: ${state}`)
await this.getLinkedInfo()//wifi连接状态改变,重新获取wifi连接信息
})
// wifi状态改变时,先清空wifi列表,然后判断是否是开启状态,如果是就扫描
wifi.on('wifiStateChange', state => {
Logger.log(TAG, `wifiStateLisener state: ${state}`)
AppStorage.SetOrCreate('wifiList', [])
if (state === 1) { // 1: wifi is enable, 0:wifi is disable
this.scan()
}
})
}
....................................
//model/WifiModel.ets
async getScanInfos(): Promise<Array<WifiType>> {
Logger.log(TAG, 'scanWifi begin')
let wifiList: Array<WifiType> = []
let result: Array<wifi.WifiScanInfo> = []
try {
result = await wifi.getScanResults()//因为在abouttoappear()中执行了wifi.scan(),所以这里直接调用getScanResults接口得到扫描结果
} catch (err) {
Logger.log(TAG, `scan info err: ${JSON.stringify(err)}`)
return wifiList
}
Logger.log(TAG, `scan info call back: ${result.length}`)
for (var i = 0; i < result.length; ++i) {
wifiList.push({
ssid: result[i].ssid,
bssid: result[i].bssid,
securityType: result[i].securityType,
rssi: result[i].rssi,
band: result[i].band,
frequency: result[i].frequency,
timestamp: result[i].timestamp
})
}
return wifiList
}
```
4.构建UI框架、通过关闭按钮实现断开wifi连接。
```typescript
build() {
Column() {
TitleBar()//引入自component/TitleBar.ets,负责标题框的具体呈现
Row() {
Text($r('app.string.wlan'))
.fontSize(22)
.fontWeight(FontWeight.Bold)
.height(40)
Column() {
Toggle({ type: ToggleType.Switch, isOn: this.isSwitchOn })//切换按钮
.id('switch')
.onChange((isOn: boolean) => {
Logger.log(`LSQ: wifi swtich is: ${isOn}`)
AppStorage.SetOrCreate('wifiList', [])
try {
// 如果按钮被打开了,记录状态,打开网络,开始扫描可用wifi
if (isOn) {
this.isSwitchOn = true
wifi.enableWifi()//使wifi功能可用
return
} else {
// 如果按钮被关闭了记录状态,断开网络禁用网络
this.isSwitchOn = false
this.isLinked = false
wifi.disconnect()//断开当前wifi连接
wifi.disableWifi()//使wifi功能不可用
}
} catch (error) {
Logger.error(TAG, `failed,code:${JSON.stringify(error.code)},message:${JSON.stringify(error.message)}`)
}
})
}
}
.width('100%')
.padding({ left: 16, right: 16 })
if (this.isLinked && this.isSwitchOn) {
//如果当前按钮处于打开状态,且wifi是已连接状态
Column() {
Text($r('app.string.connected'))
.fontSize(22)
.width('100%')
Row() {
Text(this.linkedInfo.ssid)//展示当前已连接的wifi
.fontSize(20)
.fontColor(Color.Black)
.layoutWeight(1)
Text($r('app.string.connected'))
.fontSize(18)
.fontColor(Color.Black)
}
.width('100%')
.padding(10)
.margin({ left: 16, right: 16 })
.border({ radius: 15, color: Color.Gray, width: 1 })
.backgroundColor(Color.White)
}
.width('100%')
.padding({ left: 16, right: 16 })
}
if (this.isSwitchOn) {
//如果按钮处于打开状态,展示所有可用wifi
AvailableWifi({ linkedInfo: this.linkedInfo })//AvailableWifi引入自component/AvailableWifi.ets
}
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.index_bg'))
}
```
5.展示可用wifi列表及wifi的连接。
```typescript
//component/AvailableWifi.ets
build() {
List() {
ListItem() {
Row() {
Text($r('app.string.available_wlan'))
.fontSize(22)
.layoutWeight(1)
}
.id('validWlan')
.width('100%')
}
//通过数据懒加载的方式从数据源中每次迭代一个可用wifi进行展示,可用列表被放置在滚动容器中,被划出可视区域外的资源会被回收
LazyForEach(this.wifiDataResource, (item, index) => {
ListItem() {
WifiView({ wifi: item })//WifiView引入自Component/WifiView.ets,负责可用Wifi信息展示的具体实现
}
.id(`Wifi${index}`)
//对可用wifi点击时触发事件
.onClick(() => {
Logger.info(TAG, 'wifi click')
this.scanInfo = item
if (this.linkedInfo !== null && item.ssid === this.linkedInfo.ssid) {
prompt.showToast({ message: 'this wifi is connected' })//如果当前已连接wifi并且点击的就是这个wifi,创建并显示文本提示框
return
}
if (item.securityType === 0 || item.securityType === 1) {
//如果点击的这个wifi的加密类型是无效加密类型或者开放加密类型,调用model/Wifimodel.ets中的connectNetwork方法连接此wifi
this.wifiModel.connectNetwork(item, '')
return
}
//如果点击的这个wifi的加密类型是其他加密类型,则需要输入密码,调用component/PswDialog.ets中的方法进行弹窗的生成
this.pswDialogController.open()
})
}, item => JSON.stringify(item))
}
.width('100%')
.height('100%')
.padding({ left: 16, right: 16 })
.layoutWeight(1)
.divider({ strokeWidth: 1, color: Color.Gray, startMargin: 10, endMargin: 10 })
.margin({ top: 10 })
}
//pswDialogController回调的action函数,将传回的wifi信息与密码传入model/Wifimodel.ets中的connectNetwork方法中
onAccept(scanInfo, psw) {
Logger.info(TAG, 'connect wifi')
self.wifiModel.connectNetwork(scanInfo, psw)
}
....................................
//Component/WifiView.ets
aboutToAppear() {
Logger.debug(TAG, `aboutToAppear ${JSON.stringify(this.wifi)}`)
if (this.wifi) {
if (this.wifi.securityType) {
if (this.wifi.securityType === 0 || this.wifi.securityType === 1) {
//wifi的加密类型是无效加密类型或者开放加密类型
this.securityString = $r('app.string.open')
this.isLock = false
}
}
}
}
build() {
Row() {
Column() {
if (this.wifi) {
if (this.wifi.ssid) {
Text(this.wifi.ssid)
.fontSize(20)
.width('100%')
}
}
//如果wifi的加密类型是无效加密类型或者开放加密类型,显示“开放”,反之显示“加密”
Text(this.securityString)
.fontSize(18)
.fontColor(Color.Gray)
.width('100%')
}
.layoutWeight(1)
Stack({ alignContent: Alignment.BottomEnd }) {
//如果wifi的加密类型是无效加密类型或者开放加密类型,显示不带锁的图标,反之显示带锁的图标
Image($r('app.media.wifi'))
.height(30)
.width(30)
.objectFit(ImageFit.Contain)
if (this.isLock) {
Image($r('app.media.lock'))
.objectFit(ImageFit.Contain)
.width(15)
.height(15)
}
}
.width(40)
.height(40)
.margin({ right: 10 })
}
.backgroundColor(Color.White)
.width('100%')
.padding(10)
}
}
....................................
//model/Wifimodel.ets
connectNetwork(scanInfo: wifi.WifiScanInfo, psw) {
prompt.showToast({ message: 'connecting', duration: 5000 })
Logger.debug(TAG, `connectNetwork bssid=${scanInfo.bssid}`)
// 这里因为api问题,需要声明为any,已提单
let deviceConfig: any = {
ssid: scanInfo.ssid,
bssid: scanInfo.bssid,
preSharedKey: psw,
isHiddenSsid: false,
securityType: scanInfo.securityType
}
try {
wifi.connectToDevice(deviceConfig)//连接到指定网络
Logger.debug(TAG, `connectToDevice success`)
} catch (err) {
Logger.debug(TAG, `connectToDevice fail err is ${JSON.stringify(err)}`)
}
try {
wifi.addDeviceConfig(deviceConfig)//添加网络配置
} catch (err) {
Logger.debug(TAG, `addDeviceConfig fail err is ${JSON.stringify(err)}`)
}
}
....................................
//component/PswDialog.ets
build() {
Column() {
Text(this.scanInfo.ssid)
.fontSize(20)
.width('95%')
TextInput({ placeholder: 'input password' })//密码的输入框
.id('input')
.type(InputType.Password)
.placeholderColor(Color.Gray)
.fontSize(19)
.margin({ top: 15 })
.width('95%')
.height(36)
//每当输入框中的内容变化,它就赋值给psw
.onChange((value: string) => {
this.psw = value
})
Row() {
//确认按钮
Button() {
Text($r('app.string.sure'))
.fontColor(Color.Blue)
.fontSize(17)
}
.id('sure')
.layoutWeight(7)
.backgroundColor(Color.White)
.margin(5)
.onClick(() => {
this.controller.close()
this.action(this.scanInfo, this.psw)
})
Text()
.width(1)
.height(35)
.backgroundColor($r('app.color.text_color'))
//取消按钮
Button() {
Text($r('app.string.cancel'))
.fontColor(Color.Red)
.fontSize(17)
}
.layoutWeight(7)
.backgroundColor(Color.White)
.margin(5)
.onClick(() => {
this.controller.close()
})
}
.width('100%')
.margin({ top: '3%' })
}
.padding(15)
}
```
## 全部代码
本例完整代码sample示例链接:[WLAN](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/Connectivity/Wlan)
## 参考
[权限列表](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md#ohospermissiondistributed_datasync)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册