HarmonyOS技术精讲-Camera Kit(相机服务)第3篇:实现相机预览功能

📅 2026/7/14 23:16:01
HarmonyOS技术精讲-Camera Kit(相机服务)第3篇:实现相机预览功能
《HarmonyOS技术精讲-Camera Kit相机服务第3篇实现相机预览功能》开篇Camera Kit的预览功能是几乎所有相机应用的基础模块。但在HarmonyOS NEXT开发中很多人第一次接触预览时都会遇到一个现象官方示例能运行但放到实际项目里XComponent显示黑屏、Session创建失败、权限回调处理不当。这些问题不是因为API复杂而是因为几个关键细节没有处理到位。这篇文章从零搭建一个完整的预览模块代码全部可运行重点处理生命周期、状态同步、权限管理这些实际开发里避不开的问题。核心组件Camera Kit的预览流程涉及四个关键对象组件职责生命周期CameraManager相机管理入口应用级单例CameraInput相机设备输入随会话创建和销毁PreviewOutput预览输出流随预览会话CaptureSession会话控制器管理输入输出绑定这个架构的设计思路是输入和输出分离通过会话管理连接成一整条数据通路。PreviewOutput本身不负责渲染它只输出画面数据需要XComponent作为接收端。环境说明DevEco Studio版本DevEco Studio 6.1.0及以上 HarmonyOS SDK版本HarmonyOS 6.1.0(23)及以上 目标设备手机真机完整实现1. 权限配置在module.json5中添加相机权限。这里要注意必须同时声明ohos.permission.CAMERA和ohos permission.MICROPHONE否则在某些设备上预览流会无声。{module:{requestPermissions:[{name:ohos.permission.CAMERA,reason:$string:camera_reason,usedScene:{abilities:[EntryAbility]}},{name:ohos.permission.MICROPHONE,reason:$string:microphone_reason,usedScene:{abilities:[EntryAbility]}}]}}2. 预览页面布局XComponent是ArkUI里的原生视图容器可以用来承载Camera Kit的预览数据。这里指定type为surafacelibraryname可以填空字符串。EntryComponentstruct CameraPreviewPage{StateisCameraReady:booleanfalseprivatexComponentController:XComponentControllernewXComponentController()build(){Column(){XComponent({id:camera_preview,type:suraface,libraryname:}).controller(this.xComponentController).width(100%).height(80%).onLoad((){// XComponent加载完成后初始化相机this.initCamera()})Row(){Button(切换相机).onClick(()this.switchCamera())Button(拍照).onClick(()this.takePhoto())}.width(100%).padding(20)}.width(100%).height(100%).backgroundColor(Color.Black)}}3. 相机初始化逻辑这一部分是最容易出问题的地方。CameraManager的创建要放在onLoad回调里确保XComponent的Surface已经就绪。import{camera}fromkit.CameraKitimport{common}fromkit.AbilityKitclassCameraController{privatecameraManager:camera.CameraManager|nullnullprivatecameraInput:camera.CameraInput|nullnullprivatepreviewOutput:camera.PreviewOutput|nullnullprivatecaptureSession:camera.CaptureSession|nullnullasyncinitCamera(context:common.Context,surfaceId:string){// 1. 获取CameraManagerthis.cameraManagercamera.getCameraManager(context)if(!this.cameraManager){console.error(获取CameraManager失败)return}// 2. 获取相机列表constcameraArray:Arraycamera.CameraDeviceawaitthis.cameraManager.getSupportedCameras()if(cameraArray.length0){console.error(未找到相机设备)return}// 3. 创建CaptureSessionthis.captureSessionawaitthis.cameraManager.createCaptureSession()// 4. 创建CameraInputthis.cameraInputawaitthis.cameraManager.createCameraInput(cameraArray[0])awaitthis.cameraInput.open()// 5. 创建PreviewOutputthis.previewOutputawaitthis.cameraManager.createPreviewOutput(surfaceId)// 6. 配置会话awaitthis.captureSession.beginConfig()awaitthis.captureSession.addInput(this.cameraInput)awaitthis.captureSession.addOutput(this.previewOutput)awaitthis.captureSession.commitConfig()// 7. 开始预览awaitthis.captureSession.start()}}4. 完整的页面代码把上面两个部分整合到一起注意处理生命周期和异常。import{camera}fromkit.CameraKitimport{common}fromkit.AbilityKitEntryComponentstruct CameraPreviewPage{StateisPreviewing:booleanfalseprivatexComponentController:XComponentControllernewXComponentController()privatecameraController:CameraControllernewCameraController()privatecontextgetContext(this)ascommon.Contextbuild(){Column(){if(!this.isPreviewing){Text(正在初始化相机...).fontSize(20).fontColor(Color.White).textAlign(TextAlign.Center).width(100%).height(80%)}else{XComponent({id:camera_preview,type:suraface,libraryname:}).controller(this.xComponentController).width(100%).height(80%)}Row(){Button(切换相机).onClick(()this.switchCamera())Button(停止预览).onClick(()this.stopPreview())}.width(100%).padding(20)}.width(100%).height(100%).backgroundColor(Color.Black).onPageShow((){// 页面显示时初始化相机if(!this.isPreviewing){this.initCamera()}}).onPageHide((){// 页面隐藏时释放资源this.releaseCamera()})}asyncinitCamera(){try{constsurfaceIdthis.xComponentController.getXComponentSurfaceId()awaitthis.cameraController.initCamera(this.context,surfaceId)this.isPreviewingtrue}catch(error){console.error(相机初始化失败:,JSON.stringify(error))}}stopPreview(){if(this.cameraController.captureSession){this.cameraController.captureSession.stop()}this.isPreviewingfalse}releaseCamera(){if(this.cameraController.cameraInput){this.cameraController.cameraInput.close()}if(this.cameraController.previewOutput){this.cameraController.previewOutput.release()}this.isPreviewingfalse}switchCamera(){// 切换相机逻辑}}5. 权限申请在实际设备上运行时需要在Ability中先申请权限。这里建议使用ohos.abilityAccessCtrl模块进行运行时授权。import{abilityAccessCtrl,common}fromkit.AbilityKitexportasyncfunctionrequestCameraPermission(context:common.Context):Promiseboolean{constatManagerabilityAccessCtrl.createAtManager()try{awaitatManager.requestPermissionsFromUser(context,[ohos.permission.CAMERA,ohos.permission.MICROPHONE])returntrue}catch(error){console.error(相机权限申请失败:,JSON.stringify(error))returnfalse}}常见问题坑1XComponent黑屏问题现象XComponent加载完成但画面一直黑屏。原因这个问题在HarmonyOS开发里比较常见。根本原因是Camera Manager和XComponent Surface的通信时机不对。getXComponentSurfaceId()在onLoad回调之前调用会返回空字符串导致PreviewOutput无法连接到Surface。解决方案确保在onLoad回调之后调用getXComponentSurfaceId()。推荐的做法是把相机初始化逻辑放在onLoad回调里而不是onPageShow。.onLoad((){// 此时Surface已就绪可以安全获取IDconstsurfaceIdthis.xComponentController.getXComponentSurfaceId()// 初始化相机this.initCameraWithSurfaceId(surfaceId)})坑2页面返回后预览画面无法恢复现象按返回键退出页面再次进入时预览画面无法显示。原因onPageShow和onPageHide的生命周期管理不完善。很多人只在onPageShow里初始化但onPageHide时没有正确释放CameraInput和PreviewOutput的资源。第二次进入时CameraInput还在占用相机设备新的Session无法创建。解决方案在onPageHide里调用releaseCamera()完全释放资源在onPageShow里重新初始化。.onPageHide((){this.releaseCamera()})注意如果页面跳转使用了路由栈需要在页面完全销毁时也做一次释放。建议在aboutToDisappear里也调用releaseCamera()。坑3权限拒绝后Crash现象用户拒绝相机权限后应用直接崩溃。原因createCameraInput在权限不足时会抛出异常但很多开发者没有在调用处做try-catch处理。解决方案在整个初始化方法外层包裹try-catch并在权限拒绝时给出友好提示。asyncinitCamera(){try{consthasPermissionawaitrequestCameraPermission(this.context)if(!hasPermission){// 弹出Dialog提示用户前往设置页面开启权限return}// 继续初始化}catch(error){console.error(初始化异常:,error.code,error.message)}}最佳实践CameraManager推荐做单例处理。每次重新创建CameraManager会有额外开销而且多个页面同时持有CameraManager实例可能导致设备资源冲突。建议在Application层或页面基类中持有一个全局实例。不要在build()中频繁创建CameraController对象。ArkUI的build()在状态变化时会重绘如果每次build()都new一个CameraController会导致之前的CameraInput和PreviewOutput无法被释放。推荐把CameraController作为页面成员变量初始化一次。切换相机时先stop再release再重新配置。很多人直接调用captureSession.addInput但忘了旧输入还在导致Session配置冲突。正确流程是stop - release旧Input/Output - beginConfig - addInput/addOutput - commitConfig - start。FAQQ为什么真机正常模拟器不生效AHarmonyOS NEXT模拟器对Camera Kit支持不完整。部分模拟器版本不支持createCameraInput和createPreviewOutput的正常调用。建议只在真机上进行相机相关功能的开发和测试。Q为什么页面跳转后预览画面卡住不动A这是典型的生命周期问题。如果页面跳转使用了NavigationonPageHide和onPageShow的触发时机可能和预期不一致。建议在aboutToAppear和aboutToDisappear中做更严格的资源管理。Q为什么第一次授权成功第二次进入又弹出授权弹窗A检查一下授权请求是在哪个Ability里发起的。如果应用有多个Ability需要在每个Ability里分别申请权限。推荐在EntryAbility的onCreate或onWindowStageCreate里统一申请一次后续页面通过checkAccessToken来判断权限状态。