未验证 提交 0e696bd4 编写于 作者: O openharmony_ci 提交者: Gitee

!20354 应用开发案例—ArkTS语言

Merge pull request !20354 from wangqing/master
# 分布式画布流转场景
## 场景说明
两台设备组网,当其中一个设备修改文件时,两个设备可以同步修改的结果。分布式场景可以在协同办公(如多人多设备编辑同一文件),设备文档更新(分布式设备更新文件内容,所有设备同步更新)中发挥重要作用,有助于加快工作效率,减少工作中的冗余。
本示例将为大家介绍如何实现上述功能。
## 效果呈现
本例效果如下:
| 设置分布式权限 | 进行分布式连接 | 连接后状态显示 |
| -------------------------------------- | ------------------------------------------ | ----------------------------------------- |
| ![](figures/disributed_permission.png) | ![](figures/disributed_canvas_connect.png) | ![](figures/disributed_canvas-before.png) |
| 点击rect和ellipse按钮后后本机显示 | 另外一台机器分布式应用显示 |
| ---------------------------------------- | ---------------------------------------- |
| ![](figures/disributed_canvas-after.png) | ![](figures/disributed_canvas-after.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框架
indexCanvas页面:
TitleBar组件呈现标题栏。通过数据懒加载的方式遍历绘制的图形。被划出可视区域外的资源会被回收。
绘制ellipse图形、rect图形的按钮使用Button组件呈现。
返回按钮、删除按钮也通过Button组件呈现。
```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)
//通过懒加载模式遍历绘制的图形,将每个图形绘制在画布上
LazyForEach(this.canvasDataSource, (item: CanvasPath, index) => {
Canvas(this.context)
.width('100%')
.height(200)
.backgroundColor('#00ffff')
.onReady(() => {
if (item.path === 'rect') {
this.context.save();
this.path2Df.rect(80, 80, 100, 100);
this.context.stroke(this.path2Df);
this.context.restore();
}
if (item.path === 'ellipse') {
this.context.restore();
this.path2De.ellipse(100, 100, 50, 100, Math.PI * 0.25, Math.PI * 0.5, Math.PI);
this.context.stroke(this.path2De);
this.context.save();
}
})
}, item => JSON.stringify(item))
Row() {
Button('ellipse')//绘制ellipse图形的按钮
.width(130)
.height(45)
.key('ellipse')
.onClick(() => {
if (this.globalObject.isContainString('ellipse') === -1) {
this.globalObject.add('ellipse'); //将绘制信息保存在持久全局数据中
}
this.onPageShow();
})
Button('rect')//绘制rect图形的按钮
.width(130)
.height(45)
.key('rect')
.onClick(() => {
if (this.globalObject.isContainString('rect') === -1) {
this.globalObject.add('rect');
}
this.onPageShow();
})
}.margin({ top: 10 })
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
Row() {
Button('back')
.width(130)
.height(45)
.key('back')
.backgroundColor(Color.Orange)
.onClick(() => {
router.back()
})
Button('delete')//删除图形
.width(130)
.height(45)
.key('delete')
.onClick(() => {
this.globalObject.clear();
this.canvasDataSource['pathArray'] = [];
this.canvasDataSource.notifyDataReload();
this.context.clearRect(0, 0, 950, 950)
})
}.margin({ top: 10 })
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
```
3. 数据model
通过registerDataChangeListener进行对数据变动的监听,数据发生变化时,调用notifyDataReload方法通知数据已经准备就绪。
```typescript
//BasicDataSource.ets
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = []
public totalCount(): number {
return 0
}
public getData(index: number): any {
return undefined
}
//注册数据变动的监听
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener')
this.listeners.push(listener)
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener')
this.listeners.splice(pos, 1)
}
}
//数据reloaded,分布式数据数值变化需要调用这个接口重载下
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded()
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index)
})
}
....
export class CanvasDataSource extends BasicDataSource {
//监听的数据类型
private pathArray: Canvas[] = []
//重载接口
public totalCount(): number {
return this.pathArray.length
}
public getData(index: number): any {
return this.pathArray[index]
}
public addData(index: number, data: Canvas): void {
this.pathArray.splice(index, 0, data)
this.notifyDataAdd(index)
}
public pushData(data: Canvas): void {
this.pathArray.push(data)
this.notifyDataAdd(this.pathArray.length - 1)
}
}
```
4. 将两台设备组网
使用自RemoteDeviceModel.ts中引入的类RemoteDeviceModel以扫描获得附近可以连接的设备。
```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}`)
}
}
```
5. 实现同步编辑
通过AppStorage设置持久性数据,然后实现IDataSource接口,通过注册数据监听接口监听数据的变化。
```typescript
onPageShow() {
//每当完成编辑或者新建文件,就会回到主页,此时就会执行onPageShow()
//noteDataSource获取globalObject保存的分布式的持久性数据,并进行Reload操作传递。
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)
## 参考
[权限列表](../application-dev/security/permission-list.md#ohospermissiondistributed_datasync)
[Path2D对象](../application-dev/reference/arkui-ts/ts-components-canvas-path2d.md)
[分布式数据对象](../application-dev/reference/apis/js-apis-data-distributedobject.md)
# 分布式文件场景
## 场景说明
两台设备组网的分布式场景是工作中常常需要的。常见的如代码的同步编辑、文档的同步修改等。这样的分布式场景有助于加快工作效率,减少工作中的冗余,本例将为大家介绍如何实现上述功能。
## 效果呈现
本例效果如下:
| 设置分布式权限 | 进行分布式连接 | 连接后状态显示 |
| -------------------------------------- | ---------------------------------------- | --------------------------------------- |
| ![](figures/disributed_permission.png) | ![](figures/disributed_note_connect.png) | ![](figures/disributed_note-before.png) |
| 点击添加进入编辑界面 | 保存后本机显示 | 另外一台机器分布式应用显示 |
| ------------------------------------- | -------------------------------------- | -------------------------------------- |
| ![](figures/disributed_note-edit.png) | ![](figures/disributed_note-after.png) | ![](figures/disributed_note-after.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页面:
TitleBar组件呈现标题栏。使用List组件呈现文件列表,ListItem由一个呈现文件类型标志的Image组件,一个呈现文件标题的Text组件,一个呈现文件内容的Text组件组成。
```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页面:
使用TextInput组件呈现文件标题输入框,使用TextArea组件呈现文件内容的输入区域,使用Button组件呈现保存按钮并绑定点击事件以新建或更新文件内容。
```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. 将两台设备组网
使用自RemoteDeviceModel.ts中引入的类RemoteDeviceModel以扫描获得附近可以连接的设备。
```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. 实现同步编辑
通过AppStorage设置持久性数据,然后实现IDataSource接口,通过注册数据监听接口监听数据的变化。
```typescript
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = []
public totalCount(): number {
return 0
}
public getData(index: number): any {
return undefined
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener')
this.listeners.push(listener)
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener')
this.listeners.splice(pos, 1)
}
}
//数据准备好了
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded()
})
}
...
}
onPageShow() {
//每当完成编辑或者新建文件,就会回到主页,此时就会执行onPageShow()
//noteDataSource获取globalObject保存的分布式的持久性数据,并进行Reload操作传递。
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)
## 参考
- [权限列表](../application-dev/security/permission-list.md#ohospermissiondistributed_datasync)
- [分布式数据对象](../application-dev/reference/apis/js-apis-data-distributedobject.md)
# 如何进行蓝牙连接
## 场景说明
蓝牙技术是一种无线数据和语音通信开放的全球规范,它是基于低成本的近距离无线连接,为固定和移动设备建立通信环境的一种特殊的连接。本示例通过[@ohos.bluetoothManager](../application-dev/reference/apis/js-apis-bluetoothManager.md)接口实现蓝牙设备发现、配对、取消配对功能。
## 效果呈现
本示例最终效果如下:
| 发现设备 | 连接设备 | 断开连接 |
| ------------------------------- | --------------------------------- | --------------------------------- |
| ![](figures/bluetooth_list.png) | ![](figures/bluetooth_dialog.png) | ![](figures/bluetooth_delete.png) |
## 运行环境
本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发。
- IDE:DevEco Studio 3.1.1 Release
- SDK:Ohos_sdk_full 4.0.8.5(API Version 10 Beta1)
## 实现思路
本文涉及到蓝牙的设备发现、配对、取消配对三个功能特性,实现思路如下:
- 启动和关闭蓝牙:在Index页面中通过Toggle组件的onChange函数控制蓝牙的开关,开关打开的情况下执行initBluetooth函数,关闭的情况下执行bluetooth.disableBluetooth()方法来断开蓝牙;
- 验证蓝牙是否处于连接状态:蓝牙打开的时候通过bluetooth.on('stateChange')方法监听蓝牙连接状态改变事件,如确认已打开,执行foundDevices()函数来查找设备接口,确认已关闭则执行bluetooth.stopBluetoothDiscovery()方法停止查询接口。
- 发现设备:在foundDevices()函数中通过bluetooth.on('bluetoothDeviceFind')方法监听设备发现事件,通过bluetooth.getPairedDevices()方法更新已配对蓝牙地址,然后通过bluetooth.startBluetoothDiscovery()方法开启蓝牙扫描发现远端设备,并且通过bluetooth.setBluetoothScanMode()方法来被远端设备发现。
- 蓝牙配对:通过bluetooth.on('pinRequired')方法监听远端蓝牙设备的配对请求事件,点击配对执行bluetooth.setDevicePairingConfirmation(this.data.deviceId, true)方法,点击取消执行bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)方法。
- 取消配对:使用bluetooth.cancelPairedDevice()断开指定的远端设备连接。
## 开发步骤
1. 申请蓝牙权限。
使用蓝牙需要先申请对应的权限,在module.json5文件中添加以下配置:
```json
"requestPermissions": [
{
//允许应用查看蓝牙的配置
"name": "ohos.permission.USE_BLUETOOTH",
"reason": "$string:grant_use_bluetooth",
"usedScene": {
"abilities": [
"MainAbility"
],
"when": "inuse"
}
},
{
//允许应用配置本地蓝牙,查找远端设备且与之配对连接
"name": "ohos.permission.DISCOVER_BLUETOOTH",
"reason": "$string:grant_discovery_bluetooth",
"usedScene": {
"abilities": [
"MainAbility"
],
"when": "inuse"
}
},
{
//允许应用获取设备位置信息
"name": "ohos.permission.LOCATION",
"reason": "$string:grant_location",
"usedScene": {
"abilities": [
"MainAbility"
],
"when": "inuse"
}
},
{
//允许应用获取设备模糊位置信息
"name": "ohos.permission.APPROXIMATELY_LOCATION",
"reason": "$string:grant_location",
"usedScene": {
"abilities": [
"MainAbility"
],
"when": "inuse"
}
},
{
//允许应用配对蓝牙设备,并对设备的电话簿或消息进行访问
"name": "ohos.permission.MANAGE_BLUETOOTH",
"reason": "$string:grant_manage_bluetooth",
"usedScene": {
"abilities": [
"MainAbility"
],
"when": "inuse"
}
}
]
```
2. 构建UI框架,整体的UI框架分为TitleBar(页面名称),PinDialog(配对蓝牙弹框),index(主页面)三个部分。
```ts
//Common/TitleBar.ets
@Component
export struct TitleBar {
private handlerClickButton: () => void
build() {
Row() {
Image($r('app.media.ic_back'))
.width(40)
.height(30)
.onClick(() => {
this.handlerClickButton()
})
Text($r('app.string.bluetooth'))
.fontSize(30)
.width(150)
.height(50)
.margin({ left: 15 })
.fontColor('#ffa2a3a4')
}
.width('100%')
.height(60)
.padding({ left: 20, top: 10 })
.backgroundColor('#ff2d30cb')
.constraintSize({ minHeight: 50 })
}
}
//Common/PinDalog.ets
...
aboutToAppear() {
this.titleText = `"${this.data.deviceId}"要与您配对。请确认此配对码已在"${this.data.deviceId}"上直接显示,且不是手动输入的。`
this.pinCode = JSON.stringify(this.data.pinCode)
}
build() {
//配对弹框描述文字
Column({ space: 10 }) {
Text($r('app.string.match_request'))
.fontSize(30)
.alignSelf(ItemAlign.Start)
Text(this.titleText)
.alignSelf(ItemAlign.Start)
.margin({ top: 20 })
.fontSize(21)
Text(this.pinCode)
.fontSize(40)
.fontWeight(FontWeight.Bold)
.margin({ top: 20 })
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox' })
.select(false)
.selectedColor('#ff3d6fb8')
.key('checkBox')
Text($r('app.string.grant_permission'))
.fontSize(15)
.margin({ left: 3, top: 6 })
}
.alignSelf(ItemAlign.Start)
.width('95%')
.margin({ top: 5 })
Row() {
//配对选择UI,取消or配对
this.choiceText($r('app.string.cancel'), () => {
bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)
logger.info(TAG, `setDevicePairingConfirmation = ${bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)}`)
this.controller.close()
})
Divider()
.vertical(true)
.height(32)
this.choiceText($r('app.string.match'), () => {
bluetooth.setDevicePairingConfirmation(this.data.deviceId, true)
logger.info(TAG, `setDevicePairingConfirmation = ${bluetooth.setDevicePairingConfirmation(this.data.deviceId, true)}`)
this.controller.close()
})
}
.margin({ top: 20 })
}
.width('100%')
.padding(15)
}
...
//pages/index.ets
@Entry
@Component
struct Index {
build() {
Column() {
TitleBar({ handlerClickButton: this.handlerClickButton })
Scroll() {
Column() {
Row() {
//蓝牙开关
Column() {
Text($r('app.string.bluetooth'))
.fontSize(30)
.margin({ top: 20 })
.alignSelf(ItemAlign.Start)
if (true === this.isOn) {
Text($r('app.string.discovery'))
.fontSize(20)
.alignSelf(ItemAlign.Start)
}
}
Blank()
Column() {
Toggle({ type: ToggleType.Switch, isOn: this.isOn })
.selectedColor('#ff2982ea')
.onChange((isOn: boolean) => {
if (isOn) {
this.isOn = true
this.initBluetooth()
} else {
this.isOn = false
bluetooth.disableBluetooth()
this.deviceList = []
this.discoveryList = []
}
})
}
.id('toggleBtn')
}
.width('90%')
if (this.isOn) {
Divider()
.vertical(false)
.strokeWidth(10)
.color('#ffece7e7')
.lineCap(LineCapStyle.Butt)
.margin('1%')
//已配对的设备
Text($r('app.string.paired_device'))
.fontSize(25)
.fontColor('#ff565555')
.margin({ left: '5%' })
.alignSelf(ItemAlign.Start)
ForEach(this.deviceList, (item, index) => {
Row() {
Text(item)
.fontSize(20)
}
.alignSelf(ItemAlign.Start)
.width('100%')
.height(50)
.margin({ left: '5%', top: '1%' })
.id(`pairedDevice${index}`)
.onClick(() => {
AlertDialog.show({
//取消配对
title: $r('app.string.disconnect'),
message: '此操作将会断开您与以下设备的连接:' + item,
primaryButton: {
value: $r('app.string.cancel'),
action: () => {
}
},
//确认取消
secondaryButton: {
value: $r('app.string.confirm'),
action: () => {
try {
bluetooth.cancelPairedDevice(item);
this.deviceList = bluetooth.getPairedDevices()
this.discoveryList = []
bluetooth.startBluetoothDiscovery()
} catch (err) {
console.error("errCode:" + err.code + ",errMessage:" + err.message);
}
}
}
})
})
})
Divider()
.vertical(false)
.strokeWidth(10)
.color('#ffece7e7')
.lineCap(LineCapStyle.Butt)
.margin('1%')
Text($r('app.string.available_device'))
.fontSize(25)
.fontColor('#ff565555')
.margin({ left: '5%', bottom: '2%' })
.alignSelf(ItemAlign.Start)
//可用设备列表
ForEach(this.discoveryList, (item) => {
Row() {
Text(item)
.fontSize(20)
}
.alignSelf(ItemAlign.Start)
.width('100%')
.height(50)
.margin({ left: '5%', top: '1%' })
//进行配对操作点击
.onClick(() => {
logger.info(TAG, `start bluetooth.pairDevice,item = ${item}`)
let pairStatus = bluetooth.pairDevice(item)
logger.info(TAG, `pairStatus = ${pairStatus}`)
})
Divider()
.vertical(false)
.color('#ffece7e7')
.lineCap(LineCapStyle.Butt)
.margin('1%')
})
}
}
}
.constraintSize({ maxHeight: '85%' })
}
}
}
```
3. 蓝牙开关打开关闭操作,需要打开蓝牙开关才能进行后续操作:
```ts
initBluetooth() {
bluetooth.on('stateChange', (data) => {
logger.info(TAG, `enter on stateChange`)
//判断蓝牙开关状态
if (data === bluetooth.BluetoothState.STATE_ON) {
logger.info(TAG, `enter BluetoothState.STATE_ON`)
//蓝牙打开后的相关操作
this.foundDevices()
}
if (data === bluetooth.BluetoothState.STATE_OFF) {
logger.info(TAG, `enter BluetoothState.STATE_OFF`)
bluetooth.off('bluetoothDeviceFind', (data) => {
logger.info(TAG, `offBluetoothDeviceFindData = ${JSON.stringify(data)}`)
})
//关闭
bluetooth.stopBluetoothDiscovery()
this.discoveryList = []
}
logger.info(TAG, `BluetoothState = ${JSON.stringify(data)}`)
})
//开启蓝牙
bluetooth.enableBluetooth()
}
```
4. 设置蓝牙扫描模式并开启扫描去发现设备,并订阅蓝牙设备发现上报时间获取设备列表
```ts
foundDevices() {
//订阅蓝牙设备发现上报事件
bluetooth.on('bluetoothDeviceFind', (data) => {
logger.info(TAG, `enter on bluetoothDeviceFind`)
if (data !== null && data.length > 0) {
if (this.discoveryList.indexOf(data[0]) === -1 && this.deviceList.indexOf(data[0]) === -1) {
this.discoveryList.push(data[0])
}
logger.info(TAG, `discoveryList = ${JSON.stringify(this.discoveryList)}`)
}
let list = bluetooth.getPairedDevices()
if (list !== null && list.length > 0) {
this.deviceList = list
logger.info(TAG, `deviceList = ${JSON.stringify(this.deviceList)}`)
}
})
//开启蓝牙扫描,可以发现远端设备
bluetooth.startBluetoothDiscovery()
//设置蓝牙扫描模式,可以被远端设备发现
bluetooth.setBluetoothScanMode(bluetooth.ScanMode.SCAN_MODE_CONNECTABLE_GENERAL_DISCOVERABLE, TIME)
}
```
5. 设置蓝牙扫描模式并开启扫描去发现设备,并订阅蓝牙设备发现上报时间获取设备列表
```ts
//配对确定和取消代码在PinDialog.ets文件中
//setDevicePairingConfirmation(device: string, accept: boolean): void
//device string 表示远端设备地址,例如:"XX:XX:XX:XX:XX:XX
//accept boolean 接受配对请求设置为true,否则设置为false
//订阅蓝牙配对状态改变事件,根据蓝牙状态更新设备列表
bluetooth.on('bondStateChange', (data) => {
logger.info(TAG, `enter bondStateChange`)
logger.info(TAG, `data = ${JSON.stringify(data)}`)
if (data.state === bluetooth.BondState.BOND_STATE_BONDED) {
logger.info(TAG, `BOND_STATE_BONDED`)
let index = this.discoveryList.indexOf(data.deviceId)
this.discoveryList.splice(index, 1)
this.deviceList = bluetooth.getPairedDevices()
}
if (data.state === bluetooth.BondState.BOND_STATE_INVALID) {
logger.info(TAG, `BOND_STATE_INVALID`)
this.deviceList = bluetooth.getPairedDevices()
}
logger.info(TAG, `bondStateChange,data = ${JSON.stringify(data)}`)
})
```
## 完整代码
本例完整代码sample示例链接:[蓝牙Sample](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/Connectivity/Bluetooth)
## 参考
- [权限申请指导](../application-dev/security/accesstoken-guidelines.md)
- [@ohos.bluetooth](../application-dev/reference/apis/js-apis-bluetooth.md)
\ No newline at end of file
# 如何调用设备摄像头进行拍照、预览并将拍摄结果保存在媒体库中
## 场景说明
调用设备摄像头进行拍照、预览是许多应用开发过程中都需要的功能。在拍照完成时显示照片预览图可以确认拍摄的照片是否达到预期,本例将为大家介绍如何实现上述功能。
## 效果呈现
本例效果如下:
| 拍照 | 预览 |
| :----------------------------------------------------------: | :----------------------------------------------------------: |
| <img src="figures/camera.png" alt="contactlist" style="zoom: 45%;" /> | <img src="figures/camerapreview.gif" alt="contactlist" style="zoom: 50%;" /> |
## 运行环境
本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发。
- IDE:DevEco Studio 4.0.0.201 Beta1
- SDK:Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1)
## 实现思路
本例使用@ohos.multimedia.camera接口实现相机示例的主要功能:拍照、预览;
- 拍照:XComponent组件负责绘制摄像头画面呈现的窗口,其onload事件调用cameraModel.ts的initCamera方法初始化相机功能输出画面信息。拍照动作使用Image组件实现,其onclick事件调用cameraModel.ts的takepicture方法开始拍照。
- 预览:返回相机界面点击底部左侧预览图可进入相册应用,可以在其中查看照片和录制的视频。
## 开发步骤
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以输出摄像头获取的画面,其绑定的onload方法中设定了画幅的大小。
```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. 初始化相机功能
initCamera方法通过创建相机管理器实例cameraMgr来创建画面输出对象previewOutput。cameraMgr再通过创建CaptureSession实例来配置会话,完成相机功能的准备工作。
```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. 点击按钮进行拍照
拍照按钮通过Image组件呈现,其绑定的onClick方法调用takePicture方法开始拍照。
```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);
}
```
- 保存图片
saveImage方法使用MediaModel中的createAndGetUri方法创建Image类型资源,将拍摄到的照片写入到这个资源中去。
```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)
- [@ohos.multimedia.camera](../application-dev/reference/apis/js-apis-camera.md)
# 如何实现WLAN网络扫描、连接、断开
## 场景说明
对可用的WLAN列表进行扫描,连接、断开WLAN网络是设备常见的功能,本例将为大家介绍如何实现上述功能。
## 效果呈现
本例效果如下:
| 扫描WLAN | 连接WLAN | 断开WLAN |
| :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
| <img src="figures/wlanscan.png" alt="contactlist" style="zoom: 50%;" /> | <img src="figures/wlanconnect.png" alt="contactlist" style="zoom: 50%;" /> | <img src="figures/wlandisconnect.png" alt="contactlist" style="zoom: 50%;" /> |
## 运行环境
本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发。
- IDE:DevEco Studio 4.0.0.201 Beta1
- SDK:Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1)
## 实现思路
本例主要通过@ohos.wifiManager 相关API实现WLAN激活和关闭、扫描和连接WLAN等功能。
- WLAN功能的激活和关闭:点击首页的切换按钮,如果是打开,使用wifi.enableWifi()开启WLAN功能;如果是关闭,则使用wifi.disconnect()断开WLAN功能, 然后使用wifi.disableWifi()关闭WLAN。
- WLAN的连接:点击WLAN列表中加密的WLAN,并在弹窗中输入密码后,会在AvailableWifi.ets中通过WifiModule.connectNetwork()调用wifi.connectToDevice()连接WLAN。
- WLAN的扫描:进入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. 准备工作
定义boolean变量isSwitchOn作为WLAN开关是否打开的标志。
```typescript
import wifi from '@ohos.wifiManager'
aboutToAppear() {
// 如果WLAN是开的,就记录下状态,然后扫描WLAN,并获取连接信息
if (wifi.isWifiActive()) {//查询WLAN是否已使能
Logger.log(TAG, 'wifi is active')
this.isSwitchOn = true//已使能说明WLAN开关已被打开,那么让isSwitchOn为true
wifi.scan()//@ohos.wifiManager的接口,对WLAN进行扫描
this.scan()//自定义的scan函数,确保扫描的持续性
this.getLinkedInfo()
}
// 启动监听
this.addListener()//监听连接状态的变化
}
```
3. 持续地扫描可用WLAN
scan方法不断地自我调用以实现对WLAN的不停扫描。
```typescript
async getLinkedInfo() {
try {
//调用getLinkedInfo接口得到是否已连接WLAN的信息
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() {
// 获取有关WLAN连接的信息,存入linkedInfo
await this.getLinkedInfo()
// 不停地扫描WLAN
let result: Array<WifiType> = await this.wifiModel.getScanInfos()//调用model/WifiModel.ets中的getScanInfos得到扫描结果
if (this.isSwitchOn) {
//如果WLAN功能已打开,则要进行不停地扫描
AppStorage.SetOrCreate('wifiList', result)//将扫描结果存至全局
//每3000毫秒调用一次scan(),实现不停扫描可用WLAN
setTimeout(async () => {
await this.scan()
}, 3000)
}
}
addListener() {
// WLAN连接状态改变事件发生时,修改连接信息
wifi.on('wifiConnectionChange', async state => {
Logger.log(TAG, `wifiConnectionChange: ${state}`)
await this.getLinkedInfo()//WLAN连接状态改变,重新获取WLAN连接信息
})
// WLAN状态改变时,先清空WLAN列表,然后判断是否是开启状态,如果是就扫描
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框架、通过关闭按钮实现断开WLAN连接。
TitleBar组件呈现了标题框。WLAN开关按钮由一个Toggle组件呈现,其绑定的onChange事件在按钮被打开时开始扫描可用WLAN,在被关闭时断开当前连接WLAN并且关闭WLAN功能。当前已连接的WLAN由两个Text组件分别显示其ssid与连接状态。
```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 {
// 如果按钮被打开了,记录状态,打开网络,开始扫描可用WLAN
if (isOn) {
this.isSwitchOn = true
wifi.enableWifi()//使WLAN功能可用
return
} else {
// 如果按钮被关闭了记录状态,断开网络禁用网络
this.isSwitchOn = false
this.isLinked = false
wifi.disconnect()//断开当前WLAN连接
wifi.disableWifi()//使WLAN功能不可用
}
} 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) {
//如果当前按钮处于打开状态,且WLAN是已连接状态
Column() {
Text($r('app.string.connected'))
.fontSize(22)
.width('100%')
Row() {
Text(this.linkedInfo.ssid)//展示当前已连接的WLAN
.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) {
//如果按钮处于打开状态,展示所有可用WLAN
AvailableWifi({ linkedInfo: this.linkedInfo })//AvailableWifi引入自component/AvailableWifi.ets
}
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.index_bg'))
}
```
5. 展示可用WLAN列表及WLAN的连接。
使用List组件呈现可用WLAN列表,ListItem由一个呈现WLANssid的Text组件,一个表示其是否被加密的Text组件和一个展示其加密与否图像的Image组件组成。
```typescript
//component/AvailableWifi.ets
build() {
List() {
ListItem() {
Row() {
Text($r('app.string.available_wlan'))
.fontSize(22)
.layoutWeight(1)
}
.id('validWlan')
.width('100%')
}
//通过数据懒加载的方式从数据源中每次迭代一个可用WLAN进行展示,可用列表被放置在滚动容器中,被划出可视区域外的资源会被回收
LazyForEach(this.wifiDataResource, (item, index) => {
ListItem() {
WifiView({ wifi: item })//WifiView引入自Component/WifiView.ets,负责可用WLAN信息展示的具体实现
}
.id(`Wifi${index}`)
//对可用WLAN点击时触发事件
.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' })//如果当前已连接WLAN并且点击的就是这个WLAN,创建并显示文本提示框
return
}
if (item.securityType === 0 || item.securityType === 1) {
//如果点击的这个WLAN的加密类型是无效加密类型或者开放加密类型,调用model/Wifimodel.ets中的connectNetwork方法连接此WLAN
this.wifiModel.connectNetwork(item, '')
return
}
//如果点击的这个WLAN的加密类型是其他加密类型,则需要输入密码,调用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函数,将传回的WLAN信息与密码传入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) {
//WLAN的加密类型是无效加密类型或者开放加密类型
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%')
}
}
//如果WLAN的加密类型是无效加密类型或者开放加密类型,显示“开放”,反之显示“加密”
Text(this.securityString)
.fontSize(18)
.fontColor(Color.Gray)
.width('100%')
}
.layoutWeight(1)
Stack({ alignContent: Alignment.BottomEnd }) {
//如果WLAN的加密类型是无效加密类型或者开放加密类型,显示不带锁的图标,反之显示带锁的图标
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)
- [@ohos.wifiManager](../application-dev/reference/apis/js-apis-wifiManager.md)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册