自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

OpenHarmony 掃碼自動配網(wǎng)

系統(tǒng) OpenHarmony
隨著智能手機攝像頭識別能力的提升,以及用戶需求的引領(lǐng),利用QR碼連接WiFi的方式未來還將得到更廣泛的應(yīng)用,為用戶提供更穩(wěn)定便捷的上網(wǎng)體驗。它利用了移動互聯(lián)網(wǎng)時代的技術(shù)優(yōu)勢,解決了傳統(tǒng)WiFi連接中的痛點,是一種值得推廣的網(wǎng)絡(luò)連接方式。

背景

隨著移動互聯(lián)網(wǎng)的發(fā)展,WiFi已成為人們生活中不可或缺的網(wǎng)絡(luò)接入方式。但在連接WiFi時,用戶常需要手動輸入一個復(fù)雜的密鑰,這帶來了一定的不便。針對這一痛點,利用QR碼連接WiFi的方案應(yīng)運而生。QR碼連接WiFi的工作流程是:商家或公共場所提供含有WiFi密鑰的QR碼,用戶只需使用手機掃一掃即可讀取密鑰信息并連接WiFi,無需手動輸入,這種連接方式大大簡化了用戶的操作。隨著智能手機攝像頭識別能力的提升,以及用戶需求的引領(lǐng),利用QR碼連接WiFi的方式未來還將得到更廣泛的應(yīng)用,為用戶提供更穩(wěn)定便捷的上網(wǎng)體驗。它利用了移動互聯(lián)網(wǎng)時代的技術(shù)優(yōu)勢,解決了傳統(tǒng)WiFi連接中的痛點,是一種值得推廣的網(wǎng)絡(luò)連接方式。

效果

頁面截圖

掃碼頁面


配網(wǎng)連接中

配網(wǎng)連接成功

配網(wǎng)連接失敗

在線視頻播放的地址

優(yōu)勢

使用QR碼連接WiFi具有以下優(yōu)勢:

  • 提高了連接成功率,避免因手動輸入密鑰錯誤導(dǎo)致的連接失敗問題。
  • 加快了連接速度,掃碼相對于手動輸入更高效方便。
  • 提升了用戶體驗,無需記憶和輸入復(fù)雜密鑰,操作更人性化。
  • 方便密鑰分享和更改,通過更新QR碼即可實現(xiàn)。
  • 在一些需要頻繁連接不同WiFi的場景下尤其便利,如酒店、餐廳、機場等。
  • 一些App可以自動識別WiFi二維碼,實現(xiàn)零點擊連接。

開發(fā)與實現(xiàn)

開發(fā)環(huán)境

開發(fā)平臺:windows10、DevEco Studio 3.1 Release
系統(tǒng):OpenHarmony 3.2 Release,API9(Full SDK 3.2.11.9)
設(shè)備:SD100(工業(yè)平板設(shè)備、平臺:RK3568、屏幕像素:1920 * 1200)

項目開發(fā)

需求分析

  • 支持相機掃碼,并可以解析二維碼信息。
  • 獲取二維碼中的wifi連接信息,自動完成網(wǎng)絡(luò)連接。
  • 網(wǎng)絡(luò)連接成功,則提示用戶成功。
  • 網(wǎng)絡(luò)連接失敗,則提示用戶失敗,可以重新連接。
  • UI界面符合OpenHarmony設(shè)計原則,應(yīng)用界面簡潔高效、自然流暢。

項目流程圖

界面

說明:從需求上分析,可以有兩個界面,一是掃碼界面、二是wifi連接等待和顯示結(jié)果界面。

詳細(xì)開發(fā)

一、創(chuàng)建項目

說明:通過DevEco Studio創(chuàng)建一個OpenHarmony的項目。

二、申請權(quán)限

說明:在應(yīng)用中涉及到使用相機和wifi的操作,需要動態(tài)申請一些必要的權(quán)限,我們可以在 EntryAbility.ts中實現(xiàn),EntryAbility.ts繼承UIAbility,用于管理應(yīng)用的生面周期,在OnCreate是實例冷啟動時觸發(fā),在此函數(shù)中實現(xiàn)權(quán)限申請。具體代碼如下:

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)}`)
    // 判斷授權(quán)是否完成
    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)
}

代碼解析:

  • 在應(yīng)用中使用到相機和操作wifi需要根據(jù)需要動態(tài)申請相關(guān)權(quán)限,具體的權(quán)限用途可以查看:應(yīng)用權(quán)限列表。
  • 應(yīng)用動態(tài)授權(quán)需要使用到@ohos.abilityAccessCtrl (程序訪問控制管理),通過abilityAccessCtrl.createAtManager()獲取到訪問控制對象 AtManager。
  • 通過AtManager.requestPermissionsFromUser() 拉起請求用戶授權(quán)彈窗,由用戶動態(tài)授權(quán)。
  • 授權(quán)成功后通過Emitter(@ohos.events.emitter)向主界面發(fā)送授權(quán)結(jié)果。
  • 在onDestroy()應(yīng)用退出函數(shù)中取消Emitter事件訂閱。

三、首頁

說明:首頁即為掃碼頁面,用于識別二維碼獲取二維碼信息,為網(wǎng)絡(luò)連接準(zhǔn)備。所以此頁面有有個功能,加載相機和識別二維碼。

媒體相機:

相機的啟動借鑒社區(qū)提供的代碼案例:二維碼掃碼。

  • 相機功能在CameraServices中,源碼參考CameraServices.ets。
  • 獲取相機實例使用到媒體相機接口@ohos.multimedia.camera (相機管理)。
  • 首先使用camera.getCameraManager方法獲取相機管理器,然后使用cameraManager.getSupportedCameras方法得到設(shè)備列表, 這里默認(rèn)點亮列表中的首個相機;
  • 打開相機:使用 cameraManager.createCameraInput方法創(chuàng)建CameraInput實例,調(diào)用open方法打開相機;
  • 獲取相機輸出流:使用getSupportedOutputCapability查詢相機設(shè)備在模式下支持的輸出能力,然后使用createPreviewOutput創(chuàng)建相機輸出流。
  • 獲取拍照輸出流,使用@ohos.multimedia.image接口的 createImageReceiver 方法創(chuàng)建ImageReceiver實例,并通過其getReceivingS_urfaceId()獲取S_urfaceId,通過CameraManager.createPhotoOutput()函數(shù)構(gòu)建拍照輸出流,并將imageReceive 的 S_urfaceId與其建立綁定關(guān)系。
  • 獲取相片輸出:首先使用createCaptureSession方法創(chuàng)建捕獲會話的實例,然后使用beginConfig方法配置會話,接下來使用addInput方法添加一個攝像頭輸入流,使用addOutput添加一個攝像頭和相機照片的輸出流,使用commitConfig方法提交會話配置后,調(diào)用會話的start方法開始捕獲相片輸出。
  • 這里也可以使用相機預(yù)覽流獲取圖像數(shù)據(jù),但在界面上需要預(yù)覽,所以這里需要構(gòu)建兩條預(yù)覽流,一條預(yù)覽流用于顯示,在XComponent組件中渲染,另外一條預(yù)覽流用于獲取頭像數(shù)據(jù)用于解析,根據(jù)實踐發(fā)現(xiàn),開啟兩條預(yù)覽流后,相機幀率為:7fsp,表現(xiàn)為預(yù)覽卡頓,所以為提升預(yù)覽效果,使用定時拍照的方式獲取圖像數(shù)據(jù)。
  • 獲取圖像的在SaveCameraAsset.ets中實現(xiàn),掃碼頁面啟動后每間隔1.5s調(diào)用PhotoOutput.capture()實現(xiàn)拍照,通過imageReceiver.on(‘imageArrival’)接收圖片,使用imageReceiver.readNextImage()獲取圖像對象,通過Image.getComponent()獲取圖像緩存數(shù)據(jù)。

具體實現(xiàn)代碼:

CameraService:
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'
/**
 * 拍照保存圖片回調(diào)
 */
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 識別結(jié)果
   * @param result 識別結(jié)果
   */
  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 // 相機總數(shù)
  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'))
      // 注冊監(jiān)聽相機狀態(tài)變化
      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}`)
          // 判斷本地相機還是遠(yuǎn)程相機
          if (connectionType === camera.ConnectionType.CAMERA_CONNECTION_BUILT_IN) {
            // 本地相機
            this.displayCameraDevice(QRCodeScanConst.LOCAL_DEVICE_ID, cameras[i])
          } else if (connectionType === camera.ConnectionType.CAMERA_CONNECTION_REMOTE) {
            // 遠(yuǎn)程相機 相機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
  }
  /**
   * 處理相機設(shè)備
   * @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`)
      // 判斷相機列表中是否已經(jīng)存在此相機
      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)
    }
  }
  /**
   * 創(chuàng)建相機輸入流
   * @param cameraIndex 相機下標(biāo)
   * @param deviceId 設(shè)備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
    }
  }

  /**
   * 創(chuàng)建相機預(yù)覽輸出流
   */
  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
    }
    // 獲取當(dāng)前相機設(shè)備支持的輸出能力
    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`)
      // 監(jiān)聽預(yù)覽幀開始
      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}`)
    }
  }
  /**
   *  釋放預(yù)覽輸出流
   */
  public async releasePreviewOutput() {
    Logger.info(`${TAG} releaseCamera PreviewOutput`)
    if (this.mPreviewOutput) {
      await this.mPreviewOutput.release()
      Logger.info(`${TAG} releaseCamera PreviewOutput release`)
      this.mPreviewOutput = null
    }
  }
  /**
   * 創(chuàng)建拍照輸出流
   */
  public async createPhotoOutput(functionCallback: FunctionCallBack) {
    Logger.info(`${TAG} createPhotoOutput`)
    if (!this.mCameraManager) {
      Logger.error(`${TAG} createPhotoOutput mCameraManager is null`)
      return
    }
    // 通過寬、高、圖片格式、容量創(chuàng)建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)}`)
    }
  }
  /**
   * 獲取設(shè)備的相機列表
   * @param deviceId 設(shè)備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
  }
}
SaveCameraAsset:
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 回調(diào)
   */
  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)}`)
    // 接收圖片回調(diào)
    imageReceiver.on('imageArrival', async () => {
      console.info(`${TAG} saveImage ImageArrival`)
      // 使用當(dāng)前時間命名
      imageReceiver.readNextImage((err, imageObj: image.Image) => {
        if (imageObj === undefined) {
          Logger.error(`${TAG} saveImage failed to get valid image error = ${err}`)
          return
        }
        // 根據(jù)圖像的組件類型從圖像中獲取組件緩存 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 內(nèi)置解碼庫不開源
            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`)
          }
解碼:

說明:解碼使用內(nèi)部的解碼庫因為不開源,非常抱歉,當(dāng)然可以使用開源解碼可以,如jsqr、zxing。

"dependencies": {
    "jsqr": "^1.4.0",
    "@ohos/zxing": "^2.0.0"
  }

四、配網(wǎng)協(xié)議

說明:處于通用性考慮,需要對配網(wǎng)的二維碼解析約定一個協(xié)議,也就是約定聯(lián)網(wǎng)二維碼數(shù)據(jù)的格式:##ssid##pwd##securityType

  • ssid : 熱點的SSID,編碼格式為UTF-8。
  • pwd :熱點的密鑰。
  • securityType : 加密類型,這可以參看wifiManager.WifiSecurityType。

在項目中也提供了協(xié)議解析類AnalyticResult.ts,具體代碼如下:

/**
 * 結(jié)果解析類
 */
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
  }
}

五、配網(wǎng)頁面

說明:通過對配網(wǎng)二維碼的解析獲取到熱點的ssid、密鑰、加密類型,就可以通過@ohos.wifiManager(WLAN)提供的網(wǎng)絡(luò)連接接口實現(xiàn)配網(wǎng)。因為網(wǎng)絡(luò)連接需要調(diào)用系統(tǒng)的一些驗證流程,需要消耗一些時間,為了優(yōu)化交互,需要一個網(wǎng)絡(luò)連接等待界面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'
/**
 * 網(wǎng)絡(luò)連接頁面
 */
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是開的,就記錄下狀態(tài),然后掃描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()
    // 啟動監(jiān)聽
    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)
    }
  }
  /**
   * 啟動超時任務(wù)
   */
  startTimeOut(): void {
    Logger.info(TAG, `startTimeOut`)
    this.mTimeOutId = setTimeout(() => {
      // 如果超過1分鐘沒有連接上網(wǎng)絡(luò),則認(rèn)為網(wǎng)絡(luò)連接超時
      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)
  }
  /**
   * 取消超時任務(wù)
   */
  cancelTimeOut() {
    Logger.info(TAG, `cancelTimeOut id:${this.mTimeOutId}`)
    if (this.mTimeOutId >= 0) {
      clearTimeout(this.mTimeOutId)
      this.mTimeOutId = -1
    }
  }
  // 監(jiān)聽wifi的變化
  addListener() {
    // 連接狀態(tài)改變時,修改連接信息
    wifi.on('wifiConnectionChange', async state => {
      Logger.info(TAG, `wifiConnectionChange: ${state}`)
      // 判斷網(wǎng)絡(luò)是否連接 0=斷開  1=連接
      if (state === 1) {
        this.mConnectStatus = WifiConnectStatus.SUCCESS
        this.cancelTimeOut()
      }
      await this.getLinkedInfo()
    })
    // wifi狀態(tài)改變時,先清空wifi列表,然后判斷是否是開啟狀態(tài),如果是就掃描
    wifi.on('wifiStateChange', state => {
      Logger.info(TAG, `wifiStateLisener state: ${state}`)
    })
  }
  // 獲取有關(guān)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已經(jīng)連接,需要確認(rèn)需要連接的wifi和已連接的wifi是否為相同
      let linkedSsid: string = this.linkedInfo.ssid;
      if (linkedSsid === wifiResult.ssid) {
        Logger.info(`${TAG} The same ssid`);
        this.mConnectStatus = WifiConnectStatus.SUCCESS
        return;
      }
      // 如果wifi不同,則先斷開網(wǎng)絡(luò)連接,再重新連接
      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, // 播放次數(shù)
              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'))
  }
}

整個界面比較簡單,主要顯示當(dāng)前的連接狀態(tài):連接中、連接成功、連接超時,特別強調(diào)連接超時,計劃熱點最長連接60s,如果在預(yù)定時間未連接成功,則顯示超時,超時后可以通過重新配網(wǎng)按鈕進(jìn)行重新掃碼連接,根據(jù)實際測試,在熱點未打開狀態(tài)下掃碼連接耗時平均值12s。

亮點:

界面中最大的亮點,增加了一個發(fā)光圓形的屬性動畫animation,圓形在2s內(nèi)繞著z軸旋從0度轉(zhuǎn)到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, // 播放次數(shù)
              playMode: PlayMode.Normal // 動畫模式
            })

六、網(wǎng)絡(luò)自動連接

說明:網(wǎng)絡(luò)自動連接主要是通過@ohos.wifiManager(WLAN)提供的連接接口實現(xiàn),具體代碼如下:

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 加密類型默認(rèn):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)}`)
    }
  }
}

網(wǎng)絡(luò)連接主要是通過wifi.connectToDevice(deviceConfig)實現(xiàn),其中:deviceConfig: wifi.WifiDeviceConfig為WLAN配置信息,在連接網(wǎng)絡(luò)時必填三個參數(shù)ssid、preSharedKey、securityType。

  • ssid:熱點的SSID。
  • preSharedKey:熱點密鑰。
  • securityType:加密類型。

注意:在調(diào)用connectToDevice()函數(shù)連接網(wǎng)絡(luò)時,如果網(wǎng)絡(luò)已經(jīng)連接,則需要先調(diào)用disconnect()接口斷開網(wǎng)絡(luò)后再執(zhí)行。

至此,你已經(jīng)完成了掃碼即可連接網(wǎng)絡(luò)的應(yīng)用。

想了解更多關(guān)于開源的內(nèi)容,請訪問:

51CTO 開源基礎(chǔ)軟件社區(qū)

https://ost.51cto.com

責(zé)任編輯:jianghua 來源: 51CTO 開源基礎(chǔ)軟件社區(qū)
相關(guān)推薦

2023-08-08 14:16:07

二維碼開發(fā)鴻蒙

2017-12-28 10:10:15

2019-05-21 15:15:22

掃碼乘車公交卡支付方式

2013-11-19 16:06:39

2017-10-11 11:34:24

二維碼人臉識別無人便利店

2020-01-03 16:19:24

微信微信支付移動應(yīng)用

2018-11-05 10:28:05

2018-11-23 14:00:43

AI支付寶刷手

2020-03-08 15:39:41

微信掃碼登陸二維碼

2017-09-19 11:05:20

2013-12-03 10:32:52

2016-11-30 13:35:21

掃碼信息安全

2020-04-29 09:22:10

微信更新內(nèi)測

2021-10-26 10:29:45

掃碼登錄功能

2013-09-24 14:25:38

華為智能配電華為

2014-05-06 18:00:02

電力通信EPTN解決方案華為

2013-12-09 18:12:02

華為PTN電力配網(wǎng)

2023-05-09 20:41:00

網(wǎng)絡(luò)詐騙網(wǎng)絡(luò)安全

2021-09-08 10:02:56

面試二維碼前端

2024-03-07 07:59:37

點贊
收藏

51CTO技術(shù)棧公眾號