随着移动互联网的码自发展,WiFi已成为人们生活中不可或缺的网络接入方式。但在连接WiFi时,动配用户常需要手动输入一个复杂的密钥,这带来了一定的不便。针对这一痛点,码自利用QR码连接WiFi的方案应运而生。QR码连接WiFi的动配工作流程是:商家或公共场所提供含有WiFi密钥的QR码,用户只需使用手机扫一扫即可读取密钥信息并连接WiFi,无需手动输入,这种连接方式大大简化了用户的码自操作。随着智能手机摄像头识别能力的动配提升,以及用户需求的引领,利用QR码连接WiFi的方式未来还将得到更广泛的应用,为用户提供更稳定便捷的上网体验。它利用了移动互联网时代的码自技术优势,解决了传统WiFi连接中的痛点,是一种值得推广的网络连接方式。
扫码页面 |
配网连接中 | 配网连接成功 | 配网连接失败 |
在线视频播放的地址
使用QR码连接WiFi具有以下优势:
开发平台:windows10、DevEco Studio 3.1 Release
系统:OpenHarmony 3.2 Release,API9(Full SDK 3.2.11.9)
设备:SD100(工业平板设备、平台:RK3568、屏幕像素:1920 * 1200)
说明:从需求上分析,可以有两个界面,一是扫码界面、二是wifi连接等待和显示结果界面。
说明:通过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)}
代码解析:
说明:首页即为扫码页面,用于识别二维码获取二维码信息,为网络连接准备。所以此页面有有个功能,加载相机和识别二维码。
相机的启动借鉴社区提供的代码案例:二维码扫码。
- 获取相机实例使用到媒体相机接口@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
在项目中也提供了协议解析类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@Componentstruct 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。
注意:在调用connectToDevice()函数连接网络时,如果网络已经连接,则需要先调用disconnect()接口断开网络后再执行。
至此,你已经完成了扫码即可连接网络的应用。
想了解更多关于开源的内容,请访问:
51CTO 开源基础软件社区
https://ost.51cto.com
责任编辑:jianghua 来源: 51CTO 开源基础软件社区 网络接入鸿蒙(责任编辑:知识)
东方国信(300166.SZ)公布消息:拟使用节余募集资金永久补充流动资金
国新健康:拟挂牌转让海南化纤18.96%股权和广东海虹45%股权
Redmi Buds 5评测:46dB宽频降噪,仅售199元!