HarmonyOS技术精讲-Camera Kit(相机服务)第6篇:相机设备管理与切换

发布时间:2026/7/15 10:52:40

HarmonyOS技术精讲-Camera Kit(相机服务)第6篇:相机设备管理与切换 《HarmonyOS技术精讲-Camera Kit相机服务第6篇相机设备管理与切换》开篇问题切入HarmonyOS开发里用的Camera Kit大部分功能封装得都比较合理唯独设备管理和切换这块很多人第一次接触的时候都容易踩坑。官方文档里给了相机切换的示例代码能跑但放到实际项目里你会发现几个问题切换摄像头时画面会卡顿不是API的问题而是生命周期管理没处理好监听设备状态时回调有时候不会触发这个和注册时机有关前后置切换后预览配置可能需要重置但很多人忽略了这一点这篇就专门讲清楚这几个问题代码全部是完整可运行的。相机设备管理解决什么问题Camera Kit提供的设备管理能力主要解决三个场景场景痛点解决方案获取设备列表不知道设备有哪些摄像头、位置在哪CameraManager.getSupportedCameras()状态监听外接摄像头拔插、设备被占用CameraManager.on(‘cameraStatus’)切换摄像头用户点切换按钮后画面重定向重建CameraInput 更新会话这三个场景里切换摄像头是最容易出问题的因为涉及到会话的重建、输入的释放、预览流的重新配置。环境说明DevEco Studio版本DevEco Studio 6.1.0及以上 HarmonyOS SDK版本HarmonyOS 6.1.0(23)及以上 目标设备手机核心实现1. 枚举相机设备与位置识别先获取相机管理器然后枚举设备。这一步在初始化阶段做一次就够了。import{camera}fromkit.CameraKit;import{common}fromkit.AbilityKit;// 获取相机管理器asyncfunctioninitCameraManager(context:common.BaseContext):Promisecamera.CameraManager|null{try{constcameraManagercamera.getCameraManager(context);if(!cameraManager){console.error(获取相机管理器失败);returnnull;}returncameraManager;}catch(error){console.error(相机管理器初始化异常:${JSON.stringify(error)});returnnull;}}// 枚举相机设备asyncfunctiongetCameraList(cameraManager:camera.CameraManager):Promisecamera.CameraDevice[]{try{constcameras:camera.CameraDevice[]cameraManager.getSupportedCameras();returncameras;}catch(error){console.error(枚举设备异常:${JSON.stringify(error)});return[];}}// 识别前后置functiongetCameraPosition(device:camera.CameraDevice):front|back|other{constpositiondevice.cameraPosition;switch(position){casecamera.CameraPosition.CAMERA_POSITION_FRONT:returnfront;casecamera.CameraPosition.CAMERA_POSITION_BACK:returnback;default:returnother;}}说明cameraPosition是 CameraDevice 的属性用于区分前后置返回的数组里一般至少有一个前置一个后置有些设备可能有多个相同位置的摄像头需要额外用ID做区分2. 监听相机设备状态这一步容易被忽略。如果不监听外接摄像头拔插之后代码不知道设备变了预览会挂。// 监听相机状态functionwatchCameraStatus(cameraManager:camera.CameraManager,onChange:(statusInfo:camera.CameraStatusInfo)void):void{try{cameraManager.on(cameraStatus,(statusInfo:camera.CameraStatusInfo){console.info(相机状态变更: cameraId${statusInfo.camera.cameraId}, status${statusInfo.status});onChange(statusInfo);});}catch(error){console.error(注册状态监听失败:${JSON.stringify(error)});}}// 取消监听functionunwatchCameraStatus(cameraManager:camera.CameraManager):void{try{cameraManager.off(cameraStatus);}catch(error){console.error(取消监听失败:${JSON.stringify(error)});}}注意点监听器的注册时机要在会话创建之前否则会漏掉设备状态变更事件注销时机在页面销毁时做避免内存泄漏CameraStatus枚举有三种状态APPEAR设备插入、DISAPPEAR设备拔出、AVAILABLE设备可用3. 实现切换摄像头逻辑切换的逻辑核心是停止当前会话 - 释放旧输入 - 创建新输入 - 重新配置输出流 - 启动会话。// 切换摄像头asyncfunctionswitchCamera(cameraManager:camera.CameraManager,currentSession:camera.CameraSession|null,targetDevice:camera.CameraDevice):Promisecamera.CameraSession|null{if(!cameraManager||!targetDevice){console.error(参数无效);returnnull;}try{// 1. 停止当前会话if(currentSession){awaitcurrentSession.stop();console.info(旧会话已停止);}// 2. 创建新输入constcameraInput:camera.CameraInputcameraManager.createCameraInput(targetDevice);awaitcameraInput.open();console.info(新摄像头输入已打开);// 3. 创建新会话constnewSession:camera.CameraSessioncameraManager.createCaptureSession();newSession.beginConfig();// 4. 添加输入newSession.addInput(cameraInput);// 5. 这里需要根据业务重新配置输出流// 例如预览输出、拍照输出等// newSession.addOutput(previewOutput);// newSession.addOutput(photoOutput);// 6. 提交配置awaitnewSession.commitConfig();awaitnewSession.start();console.info(新会话已启动);returnnewSession;}catch(error){console.error(切换摄像头失败:${JSON.stringify(error)});returnnull;}}关键点stop()和start()都是异步操作必须awaitaddInput()之后必须commitConfig()才会生效切换摄像头后previewOutput和photoOutput需要重新创建并添加到新会话4. 完整的切换按钮逻辑在ArkUI里实现切换按钮这里用一个State管理当前使用的摄像头。EntryComponentstruct CameraSwitcher{StateprivatecameraManager:camera.CameraManager|nullnull;StateprivatecurrentSession:camera.CameraSession|nullnull;StateprivatecameraDevices:camera.CameraDevice[][];StateprivateisFrontCamera:booleantrue;StateprivatepreviewOutput:camera.PreviewOutput|nullnull;aboutToAppear(){this.initCamera();}aboutToDisappear(){this.releaseCamera();}asyncinitCamera(){constcontextgetContext(this);this.cameraManagerawaitinitCameraManager(context);if(!this.cameraManager)return;this.cameraDevicesawaitgetCameraList(this.cameraManager);if(this.cameraDevices.length0){console.error(没有可用相机设备);return;}// 默认使用后置摄像头constbackCamerathis.cameraDevices.find(devicegetCameraPosition(device)back);if(backCamera){this.startPreview(backCamera);}}asyncstartPreview(device:camera.CameraDevice){try{// 创建CameraInputconstcameraInputthis.cameraManager!.createCameraInput(device);awaitcameraInput.open();// 创建预览输出constsurfaceIdawaitthis.getSurfaceId();constpreviewOutputthis.cameraManager!.createPreviewOutput({width:1920,height:1080},surfaceId);// 创建会话constsessionthis.cameraManager!.createCaptureSession();session.beginConfig();session.addInput(cameraInput);session.addOutput(previewOutput);awaitsession.commitConfig();awaitsession.start();this.currentSessionsession;this.previewOutputpreviewOutput;this.isFrontCameragetCameraPosition(device)front;}catch(error){console.error(启动预览失败:${JSON.stringify(error)});}}asyncgetSurfaceId():Promisestring{// 这里从XComponent获取surfaceId// 实际项目中通过XComponent的surfaceId绑定return;}asynconSwitchCamera(){if(!this.cameraManager||this.cameraDevices.length2){console.warn(没有可切换的摄像头);return;}// 找到另一个位置的摄像头consttargetPosthis.isFrontCamera?back:front;consttargetDevicethis.cameraDevices.find(devicegetCameraPosition(device)targetPos);if(!targetDevice)return;constnewSessionawaitswitchCamera(this.cameraManager,this.currentSession,targetDevice);if(newSession){this.currentSessionnewSession;this.isFrontCamera!this.isFrontCamera;}}releaseCamera(){if(this.previewOutput){this.previewOutput.release();}if(this.currentSession){// 释放会话会同时释放所有输入输出this.currentSession.release();}}build(){Column(){Button(this.isFrontCamera?切换到后置:切换到前置).onClick(()this.onSwitchCamera()).margin({top:20}).width(200).height(50)// 预览组件XComponent({id:cameraPreview,type:XComponentType.SURFACE,controller:newXComponentController()}).width(100%).height(300).margin({top:20})}.width(100%).height(100%)}}说明aboutToAppear和aboutToDisappear是组件的生命周期钩子分别对应初始化和释放XComponent 必须绑定到页面否则预览输出无法显示切换逻辑里旧会话的输入和输出会被一并释放所以不需要单独释放每个对象常见问题与踩坑记录问题1切换摄像头后预览画面黑屏现象点击切换按钮后画面变黑屏日志无异常。原因切换后没有重新创建PreviewOutput而是直接复用了旧的输出对象。旧输出绑定的surface已经被释放。解决方案每次切换后必须重新创建PreviewOutput并绑定一个新的surfaceId。问题2快速多次点击切换按钮导致崩溃现象连续点击切换按钮两三次后应用闪退。原因切换逻辑是异步的前一次切换还没完成后一次又开始了导致状态冲突。解决方案加入防抖或状态锁。StateprivateisSwitching:booleanfalse;asynconSwitchCamera(){if(this.isSwitching)return;this.isSwitchingtrue;try{// 切换逻辑}finally{this.isSwitchingfalse;}}问题3监听器未注销导致内存泄漏现象页面反复进出后内存持续增长。原因cameraManager.on(cameraStatus, ...)注册的监听器在页面销毁后没有被移除。解决方案在aboutToDisappear中调用unwatchCameraStatus。最佳实践状态集中管理把currentSession、currentDevice、isSwitching这些状态统一放到一个数据管理类里不要在多个组件里分散声明。否则切换摄像头时其他组件拿到的状态可能还是旧的。使用Watch装饰器同步UI如果切换摄像头需要更新UI比如显示前/后置标识用Watch监听状态变化而不是在异步回调里直接this.xxx ...。回调执行时组件可能已经销毁了。真机测试设备兼容性不同手机的前后置摄像头数量、分辨率支持、位置标识可能不一样。推荐在真机上测试模拟器的 Camera Kit 行为有时和真机不一致。FAQQ为什么模拟器上无法枚举到相机设备A模拟器默认不包含相机设备需要手动配置。但即使配置了部分API行为也和真机不一致。建议用真机做Camera Kit开发。Q切换摄像头时预览画面卡住或延迟怎么办A检查是否在切换过程中调用了UI更新操作。把UI更新放到commitConfig()成功之后再执行避免异步竞争。Q页面上有多个组件同时使用相机切换后状态不一致怎么处理A推荐只维护一个全局的相机会话。如果有多个页面需要使用相机可以考虑用单例模式管理相机实例。Q权限申请成功但createCameraInput仍然报错可能是什么原因A常见原因是相机设备正在被其他应用占用。可以尝试监听cameraStatus等待设备变成AVAILABLE状态再创建输入。

相关新闻