赞
踩
随着移动互联网的发展,WiFi已成为人们生活中不可或缺的网络接入方式。但在连接WiFi时,用户常需要手动输入一个复杂的密钥,这带来了一定的不便。针对这一痛点,利用QR码连接WiFi的方案应运而生。QR码连接WiFi的工作流程是:商家或公共场所提供含有WiFi密钥的QR码,用户只需使用手机扫一扫即可读取密钥信息并连接WiFi,无需手动输入,这种连接方式大大简化了用户的操作。随着智能手机摄像头识别能力的提升,以及用户需求的引领,利用QR码连接WiFi的方式未来还将得到更广泛的应用,为用户提供更稳定便捷的上网体验。它利用了移动互联网时代的技术优势,解决了传统WiFi连接中的痛点,是一种值得推广的网络连接方式。
配网连接中 | 配网连接成功 | 配网连接失败 |
使用QR码连接WiFi具有以下优势:
提高了连接成功率,避免因手动输入密钥错误导致的连接失败问题。
加快了连接速度,扫码相对于手动输入更高效方便。
提升了用户体验,无需记忆和输入复杂密钥,操作更人性化。
方便密钥分享和更改,通过更新QR码即可实现。
在一些需要频繁连接不同WiFi的场景下尤其便利,如酒店、餐厅、机场等。
一些App可以自动识别WiFi二维码,实现零点击连接。
开发平台:windows10、DevEco Studio 3.1 Release
系统:OpenHarmony 3.2 Release,API9(Full SDK 3.2.11.9)
设备:SD100(工业平板设备、平台:RK3568、屏幕像素:1920 * 1200)
▍需求分析
1、支持相机扫码,并可以解析二维码信息;
2、获取二维码中的wifi连接信息,自动完成网络连接;
3、网络连接成功,则提示用户成功;
4、网络连接失败,则提示用户失败,可以重新连接;
5、UI界面符合OpenHarmony设计原则,应用界面简洁高效、自然流畅。
▍项目流程图
▍界面
▍详细开发
一、创建项目
说明:通过DevEco Studio创建一个OpenHarmony的项目。
说明:在应用中涉及到使用相机和wifi的操作,需要动态申请一些必要的权限,我们可以在 EntryAbility.ts中实现,EntryAbility.ts继承UIAbility,用于管理应用的生面周期,在OnCreate是实例冷启动时触发,在此函数中实现权限申请。具体代码如下:
- let permissionList: Array<Permissions> = [
- "ohos.permission.GET_WIFI_INFO",
- "ohos.permission.INTERNET",
- 'ohos.permission.CAMERA',
- 'ohos.permission.READ_MEDIA',
- 'ohos.permission.WRITE_MEDIA',
- 'ohos.permission.MEDIA_LOCATION',
- 'ohos.permission.LOCATION',
- 'ohos.permission.APPROXIMATELY_LOCATION'
- ]
-
- onCreate(want, launchParam) {
- hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
- this.requestPermissions()
- }
-
- private requestPermissions() {
- let AtManager = abilityAccessCtrl.createAtManager()
- AtManager.requestPermissionsFromUser(this.context, permissionList).then(async (data) => {
- Logger.info(`${TAG} data permissions: ${JSON.stringify(data.permissions)}`)
- Logger.info(`${TAG} data authResult: ${JSON.stringify(data.authResults)}`)
- // 判断授权是否完成
- let resultCount: number = 0
- for (let result of data.authResults) {
- if (result === 0) {
- resultCount += 1
- }
- }
- let permissionResult : boolean = false
- if (resultCount === permissionList.length) {
- permissionResult = true
- }
- AppStorage.SetOrCreate(KEY_IS_PERMISSION, true)
- this.sendPermissionResult(permissionResult)
- })
- }
-
- sendPermissionResult(result : boolean) {
- let eventData: emitter.EventData = {
- data: {
- "result": result
- }
- };
-
- let innerEvent: emitter.InnerEvent = {
- eventId: EVENT_PERMISSION_ID,
- priority: emitter.EventPriority.HIGH
- };
-
- emitter.emit(innerEvent, eventData);
- Logger.info(`${TAG} sendPermissionResult`)
- }
-
- onDestroy() {
- Logger.info(`${TAG} onDestroy`)
- emitter.off(EVENT_PERMISSION_ID)
- }
代码解析
1、在应用中使用到相机和操作wifi需要根据需要动态申请相关权限,具体的权限用途可以查看:应用权限列表
2、应用动态授权需要使用到@ohos.abilityAccessCtrl (程序访问控制管理),通过abilityAccessCtrl.createAtManager()获取到访问控制对象 AtManager。
3、通过AtManager.requestPermissionsFromUser() 拉起请求用户授权弹窗,由用户动态授权。
4、授权成功后通过Emitter(@ohos.events.emitter)向主界面发送授权结果。
5、在onDestroy()应用退出函数中取消Emitter事件订阅。
说明:首页即为扫码页面,用于识别二维码获取二维码信息,为网络连接准备。所以此页面有有个功能,加载相机和识别二维码。
相机的启动借鉴社区提供的代码案例:二维码扫码
相机功能在CameraServices中,源码参考CameraServices.ets
获取相机实例使用到媒体相机接口@ohos.multimedia.camera (相机管理)。
首先使用camera.getCameraManager方法获取相机管理器,然后使用cameraManager.getSupportedCameras方法得到设备列表, 这里默认点亮列表中的首个相机;
打开相机:使用 cameraManager.createCameraInput方法创建CameraInput实例,调用open方法打开相机;
获取相机输出流:使用getSupportedOutputCapability查询相机设备在模式下支持的输出能力,然后使用createPreviewOutput创建相机输出流。
获取拍照输出流,使用@ohos.multimedia.image接口的 createImageReceiver 方法创建ImageReceiver实例,并通过其getReceivingS_urfaceId()获取S_urfaceId,通过CameraManager.createPhotoOutput()函数构建拍照输出流,并将imageReceive 的 S_urfaceId与其建立绑定关系。
获取相片输出:首先使用createCaptureSession方法创建捕获会话的实例,然后使用beginConfig方法配置会话,接下来使用addInput方法添加一个摄像头输入流,使用addOutput添加一个摄像头和相机照片的输出流,使用commitConfig方法提交会话配置后,调用会话的start方法开始捕获相片输出。
这里也可以使用相机预览流获取图像数据,但在界面上需要预览,所以这里需要构建两条预览流,一条预览流用于显示,在XComponent组件中渲染,另外一条预览流用于获取头像数据用于解析,根据实践发现,开启两条预览流后,相机帧率为:7fsp,表现为预览卡顿,所以为提升预览效果,使用定时拍照的方式获取图像数据。
获取图像的在SaveCameraAsset.ets中实现,扫码页面启动后每间隔1.5s调用PhotoOutput.capture()实现拍照,通过imageReceiver.on('imageArrival')接收图片,使用imageReceiver.readNextImage()获取图像对象,通过Image.getComponent()获取图像缓存数据。
具体实现代码:
- import camera from '@ohos.multimedia.camera';
- import image from '@ohos.multimedia.image';
- import SaveCameraAsset from './SaveCameraAsset'
- import { QRCodeScanConst, SCAN_TYPE } from './QRCodeScanConst'
- import { Logger } from '@ohos/common'
- import common from '@ohos.app.ability.common'
- let TAG: string = 'CameraService'
- /**
- * 拍照保存图片回调
- */
- export interface FunctionCallBack {
- onCaptureSuccess(thumbnail: image.PixelMap, resourceUri: string): void
- onCaptureFailure(): void
- onRecordSuccess(thumbnail: image.PixelMap): void
- onRecordFailure(): void
- /**
- * 缩略图
- */
- thumbnail(thumbnail: image.PixelMap): void
- /**
- * AI 识别结果
- * @param result 识别结果
- */
- aiResult(result: string): void
- }
- export interface PreviewCallBack {
- onFrameStart()
- onFrameEnd()
- }
- export interface MetaDataCallBack {
- onRect(rect: camera.Rect)
- }
- export default class CameraService {
- private static instance: CameraService = null
- private mCameraManager: camera.CameraManager = null
- private mCameraCount: number = 0 // 相机总数
- private mCameraMap: Map<string, Array<camera.CameraDevice>> = new Map()
- private mCurCameraDevice: camera.CameraDevice = null
- private mCameraInput: camera.CameraInput = null
- private mPreviewOutput: camera.PreviewOutput = null
- private mPreviewOutputByImage: camera.PreviewOutput = null
- private mPhotoOutput: camera.PhotoOutput = null
- private mSaveCameraAsset: SaveCameraAsset = new SaveCameraAsset()
- private mCaptureSession: camera.CaptureSession
- private mMetadataOutput: camera.MetadataOutput
- private constructor() {
- }
- /**
- * 单例
- */
- public static getInstance(): CameraService {
- if (this.instance === null) {
- this.instance = new CameraService()
- }
-
- return this.instance
- }
- /**
- * 初始化
- */
- public async initCamera(): Promise<number> {
- Logger.info(`${TAG} initCamera`)
- if (this.mCameraManager === null) {
- this.mCameraManager = camera.getCameraManager(AppStorage.Get('context'))
- // 注册监听相机状态变化
- this.mCameraManager.on('cameraStatus', (cameraStatusInfo) => {
- Logger.info(`${TAG} camera Status: ${JSON.stringify(cameraStatusInfo)}`)
- })
- // 获取相机列表
- let cameras: Array<camera.CameraDevice> = this.mCameraManager.getSupportedCameras()
- if (cameras) {
- this.mCameraCount = cameras.length
- Logger.info(`${TAG} mCameraCount: ${this.mCameraCount}`)
- if (this.mCameraCount === 0) {
- return this.mCameraCount
- }
- for (let i = 0; i < cameras.length; i++) {
- Logger.info(`${TAG} --------------Camera Info-------------`)
- const tempCameraId: string = cameras[i].cameraId
- Logger.info(`${TAG} camera_id: ${tempCameraId}`)
- Logger.info(`${TAG} cameraPosition: ${cameras[i].cameraPosition}`)
- Logger.info(`${TAG} cameraType: ${cameras[i].cameraType}`)
- const connectionType = cameras[i].connectionType
- Logger.info(`${TAG} connectionType: ${connectionType}`)
- // 判断本地相机还是远程相机
- if (connectionType === camera.ConnectionType.CAMERA_CONNECTION_BUILT_IN) {
- // 本地相机
- this.displayCameraDevice(QRCodeScanConst.LOCAL_DEVICE_ID, cameras[i])
- } else if (connectionType === camera.ConnectionType.CAMERA_CONNECTION_REMOTE) {
- // 远程相机 相机ID格式 :deviceID__Camera_cameraID 例如:3c8e510a1d0807ea51c2e893029a30816ed940bf848754749f427724e846fab7__Camera_lcam001
- const cameraKey: string = tempCameraId.split('__Camera_')[0]
- Logger.info(`${TAG} cameraKey: ${cameraKey}`)
- this.displayCameraDevice(cameraKey, cameras[i])
- }
- }
- // todo test 选择首个相机
- this.mCurCameraDevice = cameras[0]
- Logger.info(`${TAG} mCurCameraDevice: ${this.mCurCameraDevice.cameraId}`)
- }
- }
- return this.mCameraCount
- }
- /**
- * 处理相机设备
- * @param key
- * @param cameraDevice
- */
- private displayCameraDevice(key: string, cameraDevice: camera.CameraDevice) {
- Logger.info(`${TAG} displayCameraDevice ${key}`)
- if (this.mCameraMap.has(key) && this.mCameraMap.get(key)?.length > 0) {
- Logger.info(`${TAG} displayCameraDevice has mCameraMap`)
- // 判断相机列表中是否已经存在此相机
- let isExist: boolean = false
- for (let item of this.mCameraMap.get(key)) {
- if (item.cameraId === cameraDevice.cameraId) {
- isExist = true
- break
- }
- }
- // 添加列表中没有的相机
- if (!isExist) {
- Logger.info(`${TAG} displayCameraDevice not exist , push ${cameraDevice.cameraId}`)
- this.mCameraMap.get(key).push(cameraDevice)
- } else {
- Logger.info(`${TAG} displayCameraDevice has existed`)
- }
- } else {
- let cameras: Array<camera.CameraDevice> = []
- Logger.info(`${TAG} displayCameraDevice push ${cameraDevice.cameraId}`)
- cameras.push(cameraDevice)
- this.mCameraMap.set(key, cameras)
- }
- }
-
- /**
- * 创建相机输入流
- * @param cameraIndex 相机下标
- * @param deviceId 设备ID
- */
- public async createCameraInput(cameraIndex?: number, deviceId?: string) {
- Logger.info(`${TAG} createCameraInput`)
- if (this.mCameraManager === null) {
- Logger.error(`${TAG} mCameraManager is null`)
- return
- }
- if (this.mCameraCount <= 0) {
- Logger.error(`${TAG} not camera device`)
- return
- }
- if (this.mCameraInput) {
- this.mCameraInput.close()
- }
- if (deviceId && this.mCameraMap.has(deviceId)) {
- if (cameraIndex < this.mCameraMap.get(deviceId)?.length) {
- this.mCurCameraDevice = this.mCameraMap.get(deviceId)[cameraIndex]
- } else {
- this.mCurCameraDevice = this.mCameraMap.get(deviceId)[0]
- }
- }
- Logger.info(`${TAG} mCurCameraDevice: ${this.mCurCameraDevice?.cameraId}`)
- try {
- this.mCameraInput = this.mCameraManager.createCameraInput(this.mCurCameraDevice)
- Logger.info(`${TAG} mCameraInput: ${JSON.stringify(this.mCameraInput)}`)
- this.mCameraInput.on('error', this.mCurCameraDevice, (error) => {
- Logger.error(`${TAG} CameraInput error: ${JSON.stringify(error)}`)
- })
- await this.mCameraInput.open()
- } catch (err) {
- if (err) {
- Logger.error(`${TAG} failed to createCameraInput`)
- }
- }
- }
- /**
- * 释放相机输入流
- */
- public async releaseCameraInput() {
- Logger.info(`${TAG} releaseCameraInput`)
- if (this.mCameraInput) {
- try {
- await this.mCameraInput.close()
- Logger.info(`${TAG} releaseCameraInput closed`)
- } catch (err) {
- Logger.error(`${TAG} releaseCameraInput ${err}}`)
- }
- this.mCameraInput = null
- }
- }
-
- /**
- * 创建相机预览输出流
- */
- public async createPreviewOutput(s_urfaceId: string, callback?: PreviewCallBack) {
- Logger.info(`${TAG} createPreviewOutput s_urfaceId ${s_urfaceId}`)
- if (this.mCameraManager === null) {
- Logger.error(`${TAG} createPreviewOutput mCameraManager is null`)
- return
- }
- // 获取当前相机设备支持的输出能力
- let cameraOutputCap = this.mCameraManager.getSupportedOutputCapability(this.mCurCameraDevice)
- if (!cameraOutputCap) {
- Logger.error(`${TAG} createPreviewOutput getSupportedOutputCapability error}`)
- return
-
- }
- Logger.info(`${TAG} createPreviewOutput cameraOutputCap ${JSON.stringify(cameraOutputCap)}`)
- let previewProfilesArray = cameraOutputCap.previewProfiles
- let previewProfiles: camera.Profile
- if (!previewProfilesArray || previewProfilesArray.length <= 0) {
- Logger.error(`${TAG} createPreviewOutput previewProfilesArray error}`)
- previewProfiles = {
- format: 1,
- size: {
- width: QRCodeScanConst.DEFAULT_WIDTH,
- height: QRCodeScanConst.DEFAULT_HEIGHT
- }
- }
- } else {
- Logger.info(`${TAG} createPreviewOutput previewProfile length ${previewProfilesArray.length}`)
- previewProfiles = previewProfilesArray[0]
- }
- Logger.info(`${TAG} createPreviewOutput previewProfile[0] ${JSON.stringify(previewProfiles)}`)
- try {
- this.mPreviewOutput = this.mCameraManager.createPreviewOutput(previewProfiles, s_urfaceId)
- Logger.info(`${TAG} createPreviewOutput success`)
- // 监听预览帧开始
- this.mPreviewOutput.on('frameStart', () => {
- Logger.info(`${TAG} createPreviewOutput camera frame Start`)
- if (callback) {
- callback.onFrameStart()
- }
- })
- this.mPreviewOutput.on('frameEnd', () => {
- Logger.info(`${TAG} createPreviewOutput camera frame End`)
- if (callback) {
- callback.onFrameEnd()
- }
- })
- this.mPreviewOutput.on('error', (error) => {
- Logger.error(`${TAG} createPreviewOutput error: ${error}`)
- })
- } catch (err) {
- Logger.error(`${TAG} failed to createPreviewOutput ${err}`)
- }
- }
- /**
- * 释放预览输出流
- */
- public async releasePreviewOutput() {
- Logger.info(`${TAG} releaseCamera PreviewOutput`)
- if (this.mPreviewOutput) {
- await this.mPreviewOutput.release()
- Logger.info(`${TAG} releaseCamera PreviewOutput release`)
- this.mPreviewOutput = null
- }
- }
- /**
- * 创建拍照输出流
- */
- public async createPhotoOutput(functionCallback: FunctionCallBack) {
- Logger.info(`${TAG} createPhotoOutput`)
- if (!this.mCameraManager) {
- Logger.error(`${TAG} createPhotoOutput mCameraManager is null`)
- return
- }
- // 通过宽、高、图片格式、容量创建ImageReceiver实例
- const receiver: image.ImageReceiver = image.createImageReceiver(QRCodeScanConst.DEFAULT_WIDTH, QRCodeScanConst.DEFAULT_HEIGHT, image.ImageFormat.JPEG, 8)
- const imageS_urfaceId: string = await receiver.getReceivingS_urfaceId()
- Logger.info(`${TAG} createPhotoOutput imageS_urfaceId: ${imageS_urfaceId}`)
- let cameraOutputCap = this.mCameraManager.getSupportedOutputCapability(this.mCurCameraDevice)
- Logger.info(`${TAG} createPhotoOutput cameraOutputCap ${cameraOutputCap}`)
- if (!cameraOutputCap) {
- Logger.error(`${TAG} createPhotoOutput getSupportedOutputCapability error}`)
- return
- }
-
- let photoProfilesArray = cameraOutputCap.photoProfiles
- let photoProfiles: camera.Profile
- if (!photoProfilesArray || photoProfilesArray.length <= 0) {
- // 使用自定义的配置
- photoProfiles = {
- format: camera.CameraFormat.CAMERA_FORMAT_JPEG,
- size: {
- width: QRCodeScanConst.DEFAULT_WIDTH,
- height: QRCodeScanConst.DEFAULT_HEIGHT
- }
- }
- } else {
- Logger.info(`${TAG} createPhotoOutput photoProfile length ${photoProfilesArray.length}`)
- photoProfiles = photoProfilesArray[0]
- }
- Logger.info(`${TAG} createPhotoOutput photoProfile ${JSON.stringify(photoProfiles)}`)
- try {
- this.mPhotoOutput = this.mCameraManager.createPhotoOutput(photoProfiles, imageS_urfaceId)
- Logger.info(`${TAG} createPhotoOutput mPhotoOutput success`)
- // 保存图片
- this.mSaveCameraAsset.saveImage(receiver, functionCallback)
- } catch (err) {
- Logger.error(`${TAG} createPhotoOutput failed to createPhotoOutput ${err}`)
- }
- }
- /**
- * 释放拍照输出流
- */
- public async releasePhotoOutput() {
- Logger.info(`${TAG} releaseCamera PhotoOutput`)
- if (this.mPhotoOutput) {
- await this.mPhotoOutput.release()
- Logger.info(`${TAG} releaseCamera PhotoOutput release`)
- this.mPhotoOutput = null
- }
- }
- public async createSession() {
- Logger.info(`${TAG} createSession`)
- this.mCaptureSession = await this.mCameraManager.createCaptureSession()
- Logger.info(`${TAG} createSession mCaptureSession ${this.mCaptureSession}`)
- this.mCaptureSession.on('error', (error) => {
- Logger.error(`${TAG} CaptureSession error ${JSON.stringify(error)}`)
- })
- try {
- this.mCaptureSession?.beginConfig()
- this.mCaptureSession?.addInput(this.mCameraInput)
-
- if (this.mPreviewOutputByImage != null) {
- Logger.info(`${TAG} createSession addOutput PreviewOutputByImage`)
- this.mCaptureSession?.addOutput(this.mPreviewOutputByImage)
- }
-
- if (this.mPreviewOutput != null) {
- Logger.info(`${TAG} createSession addOutput PreviewOutput`)
- this.mCaptureSession?.addOutput(this.mPreviewOutput)
- }
- if (this.mPhotoOutput != null) {
- Logger.info(`${TAG} createSession addOutput PhotoOutput`)
- this.mCaptureSession?.addOutput(this.mPhotoOutput)
- }
- if (this.mMetadataOutput != null) {
- Logger.info(`${TAG} createSession addOutput mMetadataOutput`)
-
- this.mCaptureSession?.addOutput(this.mMetadataOutput)
- }
- } catch (err) {
- if (err) {
- Logger.error(`${TAG} createSession beginConfig fail err:${JSON.stringify(err)}`)
- }
- }
- try {
- await this.mCaptureSession?.commitConfig()
- } catch (err) {
- if (err) {
- Logger.error(`${TAG} createSession commitConfig fail err:${JSON.stringify(err)}`)
- }
- }
- try {
- await this.mCaptureSession?.start()
- } catch (err) {
- if (err) {
- Logger.error(`${TAG} createSession start fail err:${JSON.stringify(err)}`)
- }
- }
- if (this.mMetadataOutput) {
- this.mMetadataOutput.start().then(() => {
- Logger.info(`${TAG} Callback returned with metadataOutput started`)
- }).catch((err) => {
- Logger.error(`${TAG} Failed to metadataOutput start ${err.code}`)
- })
- }
- Logger.info(`${TAG} createSession mCaptureSession start`)
- }
- public async releaseSession() {
- Logger.info(`${TAG} releaseCamera Session`)
- if (this.mCaptureSession) {
- await this.mCaptureSession.release()
- Logger.info(`${TAG} releaseCamera Session release`)
- this.mCaptureSession = null
- }
- }
- /**
- * 拍照
- */
- public async takePicture() {
- Logger.info(`${TAG} takePicture`)
- if (!this.mCaptureSession) {
- Logger.info(`${TAG} takePicture session is release`)
- return
- }
- if (!this.mPhotoOutput) {
- Logger.info(`${TAG} takePicture mPhotoOutput is null`)
- return
- }
- try {
- const photoCaptureSetting: camera.PhotoCaptureSetting = {
- quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
- rotation: camera.ImageRotation.ROTATION_0,
- location: {
- latitude: 0,
- longitude: 0,
- altitude: 0
- },
- mirror: false
- }
- await this.mPhotoOutput.capture(photoCaptureSetting)
- } catch (err) {
- Logger.error(`${TAG} takePicture err:${JSON.stringify(err)}`)
- }
- }
- /**
-
- * 获取设备的相机列表
- * @param deviceId 设备ID
- */
- public getDeviceCameras(deviceId: string): Array<camera.CameraDevice> {
- Logger.info(`${TAG} getDeviceCameras ${deviceId} size ${this.mCameraMap.size}`)
- return this.mCameraMap.get(deviceId)
- }
- public getCameraCount(): number {
- return this.mCameraCount
- }
- /**
- * 释放相机
- */
- public async releaseCamera(): Promise<boolean> {
- Logger.info(`${TAG} releaseCamera`)
- let result: boolean = false
- let tempStartTime: number = new Date().getTime()
- try {
- await this.releaseCameraInput()
- await this.releasePhotoOutput()
- await this.releasePreviewOutput()
- await this.releaseSession()
- result = true
- } catch (err) {
- Logger.error(`${TAG} releaseCamera fail ${JSON.stringify(err)}`)
- }
- let tempTime: number = new Date().getTime() - tempStartTime
- Logger.info(`${TAG} releaseCamera finish time: ${tempTime}`)
- return result
- }
- public async selectPic() {
- Logger.info("getSingleImageFromAlbum start")
- let context = AppStorage.Get('context') as common.UIAbilityContext
- let abilityResult = await context.startAbilityForResult({
- bundleName: 'com.ohos.photos',
- abilityName: 'com.ohos.photos.MainAbility',
- parameters: {
- uri: 'singleselect' // 只选取单个文件
- }
- })
- if (abilityResult.want === null || abilityResult.want === undefined) {
- Logger.info("getSingleImageFromAlbum end. abilityResult.want is null.")
- return null
- }
- if (abilityResult.want.parameters === null || abilityResult.want.parameters === undefined) {
- Logger.info("getSingleImageFromAlbum end. abilityResult.want.parameters is null.")
- return null
- }
- let images = abilityResult.want.parameters['select-item-list']
- let imageUri = images[0]
- Logger.info("getSingleImageFromAlbum end. uri:" + imageUri)
- return imageUri
- }
- }
- import image from '@ohos.multimedia.image'
- import { FunctionCallBack } from './CameraService'
- import { Logger } from '@ohos/common'
- import CodeRuleUtil from '../utils/CodeRuleUtil'
-
-
- const TAG: string = 'SaveCameraAsset'
-
- /**
- * 保存相机拍照的资源
- */
- export default class SaveCameraAsset {
- constructor() {
-
- }
-
- /**
- * 保存拍照图片
- * @param imageReceiver 图像接收对象
- * @param thumbWidth 宽度
- * @param thumbHeight 高度
- * @param callback 回调
- */
- public saveImage(imageReceiver: image.ImageReceiver, callback: FunctionCallBack) {
- console.info(`${TAG} saveImage`)
- let buffer = new ArrayBuffer(4096)
- const imgWidth: number = imageReceiver.size.width
- const imgHeight: number = imageReceiver.size.height
- Logger.info(`${TAG} saveImage size ${JSON.stringify(imageReceiver.size)}`)
- // 接收图片回调
- imageReceiver.on('imageArrival', async () => {
- console.info(`${TAG} saveImage ImageArrival`)
- // 使用当前时间命名
- imageReceiver.readNextImage((err, imageObj: image.Image) => {
- if (imageObj === undefined) {
- Logger.error(`${TAG} saveImage failed to get valid image error = ${err}`)
- return
- }
- // 根据图像的组件类型从图像中获取组件缓存 4-JPEG类型
- imageObj.getComponent(image.ComponentType.JPEG, async (errMsg, imgComponent) => {
- if (imgComponent === undefined) {
- Logger.error(`${TAG} getComponent failed to get valid buffer error = ${errMsg}`)
- return
- }
- if (imgComponent.byteBuffer) {
- Logger.info(`${TAG} getComponent imgComponent.byteBuffer ${imgComponent.byteBuffer.byteLength}`)
- buffer = imgComponent.byteBuffer
- // todo 内置解码库不开源
- let resultRGB: string = qr.decode(buffer)
- Logger.info(`${TAG} AI uimg result RGB ${resultRGB}`)
- if (callback) {
- callback.aiResult(CodeRuleUtil.getRuleResult(resultRGB))
- }
- } else {
- Logger.info(`${TAG} getComponent imgComponent.byteBuffer is undefined`)
- }
说明:解码使用内部的解码库因为不开源,非常抱歉,当然可以使用开源解码可以,如jsqr、zxing
- "dependencies": {
- "jsqr": "^1.4.0",
- "@ohos/zxing": "^2.0.0"
- }
说明:处于通用性考虑,需要对配网的二维码解析约定一个协议,也就是约定联网二维码数据的格式:##ssid##pwd##securityType
ssid :热点的SSID,编码格式为UTF-8。
pwd :热点的密钥
securityType :加密类型,这可以参看wifiManager.WifiSecurityType
在项目中也提供了协议解析类AnalyticResult.ts,具体代码如下:
- /**
- * 结果解析类
- */
- export type ResultType = {
- ssid: string,
- pwd: string,
- securityType : number
- }
-
- const SEPARATOR: string = '##'
-
- export class Analytic {
- constructor() {
-
- }
-
- getResult(msg: string): ResultType {
- let result: ResultType = null
- if (msg && msg.length > 0 && msg.indexOf(SEPARATOR) >= 0) {
- let resultArr: string[] = msg.split(SEPARATOR)
- if (resultArr.length >= 4) {
- result = {
- ssid: resultArr[1],
- pwd: resultArr[2],
- securityType: parseInt(resultArr[3])
- }
- }
- }
- return result
- }
- }
说明:通过对配网二维码的解析获取到热点的ssid、密钥、加密类型,就可以通过@ohos.wifiManager(WLAN)提供的网络连接接口实现配网。因为网络连接需要调用系统的一些验证流程,需要消耗一些时间,为了优化交互,需要一个网络连接等待界面ConnectPage.ets,界面截图如下:
具体代码如下:
- import { WifiConnectStatus } from '../model/Constant'
- import router from '@ohos.router';
- import { Logger } from '@ohos/common'
- import wifi from '@ohos.wifiManager';
- import { ResultType } from '../model/AnalyticResult'
- import { WifiModel } from '../model/WifiModel'
- /**
- * 网络连接页面
- */
- const TAG: string = '[ConnectPage]'
- const MAX_TIME_OUT: number = 60000 // 最大超时时间
- @Entry
- @Component
- struct ConnectPage {
- @State mConnectSsid: string = ''
- @State mConnectStatus: WifiConnectStatus = WifiConnectStatus.CONNECTING
- @State mConnectingAngle : number = 0
- @State mConnectFailResource : Resource = $r('app.string.connect_wifi_fail')
- private linkedInfo: wifi.WifiLinkedInfo = null
- private mWifiModel: WifiModel = new WifiModel()
- private mTimeOutId: number = -1
- private mAnimationTimeOutId : number = -1
- async aboutToAppear() {
- Logger.info(`${TAG} aboutToAppear`)
- this.showConnecting()
- let wifiResult: ResultType = router.getParams()['wifiResult']
- Logger.info(`${TAG} wifiResult : ${JSON.stringify(wifiResult)}`)
- // 如果wifi是开的,就记录下状态,然后扫描wifi,并获取连接信息
- if (!wifi.isWifiActive()) {
- Logger.info(TAG, 'enableWifi')
- try {
- wifi.enableWifi()
- } catch (error) {
- Logger.error(`${TAG} wifi enable fail, ${JSON.stringify(error)}`)
- }
- }
- await this.getLinkedInfo()
- // 启动监听
- this.addListener()
- if (wifiResult == null) {
- Logger.info(TAG, 'wifiResult is null')
- this.mConnectFailResource = $r('app.string.scan_code_data_error')
- this.mConnectStatus = WifiConnectStatus.FAIL
- } else {
- this.mConnectSsid = wifiResult.ssid
- Logger.info(`${TAG} connect wifi ${this.mConnectSsid}`)
- this.disposeWifiConnect(wifiResult)
- }
- }
- /**
- * 启动超时任务
- */
- startTimeOut(): void {
- Logger.info(TAG, `startTimeOut`)
- this.mTimeOutId = setTimeout(() => {
- // 如果超过1分钟没有连接上网络,则认为网络连接超时
- try {
- this.mConnectFailResource = $r('app.string.connect_wifi_fail')
- this.mConnectStatus = WifiConnectStatus.FAIL
- wifi.disconnect();
- } catch (error) {
- Logger.error(TAG, `failed,code:${JSON.stringify(error.code)},message:${JSON.stringify(error.message)}`)
- }
- }, MAX_TIME_OUT)
- }
- /**
- * 取消超时任务
- */
- cancelTimeOut() {
- Logger.info(TAG, `cancelTimeOut id:${this.mTimeOutId}`)
- if (this.mTimeOutId >= 0) {
- clearTimeout(this.mTimeOutId)
- this.mTimeOutId = -1
- }
- }
- // 监听wifi的变化
- addListener() {
- // 连接状态改变时,修改连接信息
- wifi.on('wifiConnectionChange', async state => {
- Logger.info(TAG, `wifiConnectionChange: ${state}`)
- // 判断网络是否连接 0=断开 1=连接
- if (state === 1) {
- this.mConnectStatus = WifiConnectStatus.SUCCESS
- this.cancelTimeOut()
- }
- await this.getLinkedInfo()
- })
- // wifi状态改变时,先清空wifi列表,然后判断是否是开启状态,如果是就扫描
- wifi.on('wifiStateChange', state => {
- Logger.info(TAG, `wifiStateLisener state: ${state}`)
- })
- }
- // 获取有关Wi-Fi连接的信息,存入linkedInfo
- async getLinkedInfo() {
- try {
- let wifiLinkedInfo = await wifi.getLinkedInfo()
-
- if (wifiLinkedInfo === null || wifiLinkedInfo.bssid === '') {
- this.linkedInfo = null
- return
- }
- this.linkedInfo = wifiLinkedInfo
- } catch (err) {
- Logger.info(`getLinkedInfo failed err is ${JSON.stringify(err)}`)
- }
- }
- /**
- * 处理wifi连接
- * @param wifiResult
- */
- disposeWifiConnect(wifiResult: ResultType): void {
- this.mConnectStatus = WifiConnectStatus.CONNECTING
- if (this.linkedInfo) {
- // 说明wifi已经连接,需要确认需要连接的wifi和已连接的wifi是否为相同
- let linkedSsid: string = this.linkedInfo.ssid;
- if (linkedSsid === wifiResult.ssid) {
- Logger.info(`${TAG} The same ssid`);
- this.mConnectStatus = WifiConnectStatus.SUCCESS
- return;
- }
- // 如果wifi不同,则先断开网络连接,再重新连接
- try {
- wifi.disconnect();
- this.connectWifi(wifiResult.ssid, wifiResult.pwd, wifiResult.securityType)
- } catch (error) {
- Logger.error(TAG, `failed,code:${JSON.stringify(error.code)},message:${JSON.stringify(error.message)}`)
- }
- } else {
- this.connectWifi(wifiResult.ssid, wifiResult.pwd, wifiResult.securityType)
- }
- }
- private connectWifi(ssid: string, pwd: string, securityType : number) {
- this.startTimeOut()
- this.mWifiModel.connectNetwork(ssid, pwd, securityType)
- }
- async gotoIndex() {
- try {
- let options: router.RouterOptions = {
- url: "pages/Index"
- }
- await router.replaceUrl(options)
- } catch (error) {
- Logger.error(`${TAG} go to index fail, err: ${JSON.stringify(error)}`)
- }
- }
- showConnecting() {
- this.mConnectingAngle = 0
- this.mAnimationTimeOutId = setTimeout(() => {
- this.mConnectingAngle = 360
- }, 500)
- }
- closeConnecting() {
- if (this.mAnimationTimeOutId > -1) {
- clearTimeout(this.mAnimationTimeOutId)
- }
- }
- aboutToDisappear() {
- wifi.off('wifiConnectionChange')
-
- wifi.off('wifiStateChange')
- this.cancelTimeOut()
- this.closeConnecting()
- }
- build() {
- Column() {
- // back
- Row() {
- Image($r('app.media.icon_back'))
- .width(30)
- .height(30)
- .objectFit(ImageFit.Contain)
- .onClick(() => {
- router.back()
- })
- }
- .width('90%')
- .height('10%')
- .justifyContent(FlexAlign.Start)
- .alignItems(VerticalAlign.Center)
- Stack() {
- // 背景
- Column() {
- Image($r('app.media.bg_connect_wifi'))
- .width('100%')
- .height('100%')
- .objectFit(ImageFit.Contain)
- .rotate({
- x: 0,
- y: 0,
- z: 1,
- centerX: '50%',
- centerY: '49%',
- angle: this.mConnectingAngle
- })
- .animation({
- duration: 2000, // 动画时长
- curve: Curve.Linear, // 动画曲线
- delay: 0, // 动画延迟
- iterations: -1, // 播放次数
- playMode: PlayMode.Normal // 动画模式
- })
- }
- Column({ space: 20 }) {
- if (this.mConnectStatus === WifiConnectStatus.SUCCESS) {
- // 连接成功
- Image($r('app.media.icon_connect_wifi_success'))
- .width(80)
- .height(80)
- .objectFit(ImageFit.Contain)
- Text($r('app.string.connect_wifi_success'))
- .fontSize(32)
- .fontColor($r('app.color.connect_wifi_text'))
- Text(this.mConnectSsid)
- .fontSize(22)
- .fontColor($r('app.color.connect_wifi_text'))
- } else if (this.mConnectStatus === WifiConnectStatus.FAIL) {
- // 连接失败
- Image($r('app.media.icon_connect_wifi_fail'))
- .width(80)
- .height(80)
- .objectFit(ImageFit.Contain)
- Text(this.mConnectFailResource)
- .fontSize(32)
- .fontColor($r('app.color.connect_wifi_text'))
- Button($r('app.string.reconnect_wifi'))
- .width(260)
- .height(55)
- .backgroundColor($r('app.color.connect_fail_but_bg'))
- .onClick(() => {
- this.gotoIndex()
- })
- } else {
- // 连接中
-
-
-
-
- Image($r('app.media.icon_connect_wifi'))
- .width(100)
- .height(100)
- .objectFit(ImageFit.Contain)
- Text($r('app.string.connect_wifi_hint'))
- .fontSize(16)
- .fontColor($r('app.color.connect_wifi_text'))
- Text($r('app.string.connecting_wifi'))
- .fontSize(32)
- .fontColor($r('app.color.connect_wifi_text'))
- Text(this.mConnectSsid)
- .fontSize(22)
- .fontColor($r('app.color.connect_wifi_text'))
- }
- }
- .width('100%')
- .height('100%')
- .justifyContent(FlexAlign.Center)
- .alignItems(HorizontalAlign.Center)
- }
- .width('100%')
- .height('80%')
- }
- .width('100%')
- .height('100%')
- .backgroundColor($r('app.color.connect_bg'))
- }
- }
整个界面比较简单,主要显示当前的连接状态:连接中、连接成功、连接超时,特别强调连接超时,计划热点最长连接60s,如果在预定时间未连接成功,则显示超时,超时后可以通过重新配网按钮进行重新扫码连接,根据实际测试,在热点未打开状态下扫码连接耗时平均值12s。
界面中最大的亮点,增加了一个发光圆形的属性动画animation,圆形在2s内绕着z轴旋从0度转到360度。
- Image($r('app.media.bg_connect_wifi'))
- .width('100%')
- .height('100%')
- .objectFit(ImageFit.Contain)
- .rotate({
- x: 0,
- y: 0,
- z: 1,
- centerX: '50%',
- centerY: '49%',
- angle: this.mConnectingAngle
- })
- .animation({
- duration: 2000, // 动画时长
- curve: Curve.Linear, // 动画曲线
- delay: 0, // 动画延迟
- iterations: -1, // 播放次数
- playMode: PlayMode.Normal // 动画模式
- })
说明:网络自动连接主要是通过@ohos.wifiManager(WLAN)提供的连接接口实现,具体代码如下:
- import wifi from '@ohos.wifiManager'
- import { Logger } from '@ohos/common'
- const TAG: string = '[WiFiModel]'
- export type WifiType = {
- ssid: string,
- bssid: string,
- securityType: wifi.WifiSecurityType,
- rssi: number,
- band: number,
- frequency: number,
- timestamp: number
- }
- export class WifiModel {
- async getScanInfos(): Promise<Array<WifiType>> {
- Logger.info(TAG, 'scanWifi begin')
- let wifiList: Array<WifiType> = []
- let result: Array<wifi.WifiScanInfo> = []
- try {
- result = await wifi.getScanResults()
- } catch (err) {
- Logger.info(TAG, `scan info err: ${JSON.stringify(err)}`)
- return wifiList
- }
- Logger.info(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
- }
- connectNetwork(wifiSsid: string, psw: string, securityType : number): void {
- Logger.debug(TAG, `connectNetwork bssid=${wifiSsid} securityType:${securityType}`)
- // securityType 加密类型默认:Pre-shared key (PSK)加密类型
- let deviceConfig: wifi.WifiDeviceConfig = {
- ssid: wifiSsid,
- preSharedKey: psw,
- isHiddenSsid: false,
- securityType: securityType
- }
- try {
- wifi.connectToDevice(deviceConfig)
- Logger.info(TAG, `connectToDevice success`)
- } catch (err) {
- Logger.error(TAG, `connectToDevice fail err is ${JSON.stringify(err)}`)
- }
- try {
- wifi.addDeviceConfig(deviceConfig)
- } catch (err) {
- Logger.error(TAG, `addDeviceConfig fail err is ${JSON.stringify(err)}`)
- }
- }
- }
网络连接主要是通过wifi.connectToDevice(deviceConfig)实现,其中:deviceConfig: wifi.WifiDeviceConfig为WLAN配置信息,在连接网络时必填三个参数ssid、preSharedKey、securityType。
ssid:热点的SSID
preSharedKey:热点密钥
securityType:加密类型
注意:在调用connectToDevice()函数连接网络时,如果网络已经连接,则需要先调用disconnect()接口断开网络后再执行。
至此,你已经完成了扫码即可连接网络的应用。
如果您能看到最后,还希望您能动动手指点个赞,一个人能走多远关键在于与谁同行,我用跨越山海的一路相伴,希望得到您的点赞。
有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?但是又不知道从哪里下手,而且学习时频繁踩坑,最终浪费大量时间。所以本人整理了一些比较合适的鸿蒙(HarmonyOS NEXT)学习路径和一些资料的整理供小伙伴学习
点击领取→纯血鸿蒙Next全套最新学习资料希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取~~
一、鸿蒙(HarmonyOS NEXT)最新学习路线
有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)…等技术知识点。
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
二、HarmonyOS Next 最新全套视频教程
三、《鸿蒙 (OpenHarmony)开发基础到实战手册》
OpenHarmony北向、南向开发环境搭建
四、大厂面试必问面试题
五、鸿蒙南向开发技术
六、鸿蒙APP开发必备
完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料
总结
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。