HarmonyOS技术精讲-Camera Kit(相机服务)第13篇:相机启动与恢复机制 HarmonyOS技术精讲-Camera Kit相机服务第13篇相机启动与恢复机制这个机制解决什么问题相机服务在实际开发中相较于其他多媒体能力有一个明显区别它是一个独占式硬件资源。屏幕旋转、应用切换、横竖屏变化、系统来电等场景会导致相机资源被系统回收此时如果应用没有正确处理恢复逻辑用户会看到一个黑屏或直接闪退。官方文档把Camera Kit的恢复机制描述得比较简略只提到了onBackground和onForeground回调。但在实际项目中这两个回调中间的Session状态管理才是最麻烦的部分——因为Camera Kit在后台时系统可能会做以下几件事释放相机资源CameraManager被回收断开会话连接createCaptureSession返回新的Session对象重置所有配置滤镜、闪光灯、缩放比全部清空所以这个机制的核心目标只有一个当应用从后台切回前台时让相机恢复到暂停前的状态。环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机、平板核心实现相机生命周期管理第一步定义相机状态管理类需要维护一个全局状态记录相机当前处于哪个阶段。不能每次恢复都重新初始化全部对象那样资源开销太大。// CameraStateManager.etsimport{camera}fromkit.CameraKit;import{BusinessError}fromkit.BasicServicesKit;exportenumCameraState{INITIALIZEDinitialized,// Camera对象创建完成SESSION_CREATEDsession_created,// 会话已创建PREVIEW_STARTEDpreview_started,// 预览已启动BACKGROUNDbackground// 应用进入后台资源已释放}exportclassCameraStateManager{privatecameraState:CameraStateCameraState.INITIALIZED;privatecameraManager:camera.CameraManager|nullnull;privatecameraInput:camera.CameraInput|nullnull;privatecaptureSession:camera.CaptureSession|nullnull;privatepreviewOutput:camera.PreviewOutput|nullnull;privatelastCameraId:string;privateisInBackground:booleanfalse;getstate():CameraState{returnthis.cameraState;}setState(state:CameraState):void{this.cameraStatestate;}getmanager():camera.CameraManager|null{returnthis.cameraManager;}setmanager(value:camera.CameraManager|null){this.cameraManagervalue;}getinput():camera.CameraInput|null{returnthis.cameraInput;}setinput(value:camera.CameraInput|null){this.cameraInputvalue;}getsession():camera.CaptureSession|null{returnthis.captureSession;}setsession(value:camera.CaptureSession|null){this.captureSessionvalue;}getpreview():camera.PreviewOutput|null{returnthis.previewOutput;}setpreview(value:camera.PreviewOutput|null){this.previewOutputvalue;}getcameraId():string{returnthis.lastCameraId;}setcameraId(value:string){this.lastCameraIdvalue;}getbackground():boolean{returnthis.isInBackground;}setbackground(value:boolean){this.isInBackgroundvalue;}// 释放所有资源releaseAllResources():void{this.previewOutput?.release();this.cameraInput?.release();this.captureSession?.release();this.cameraManagernull;this.captureSessionnull;this.cameraInputnull;this.previewOutputnull;this.stateCameraState.INITIALIZED;}// 释放Session保留CameraManager和InputreleaseSessionOnly():void{if(this.captureSession){this.captureSession.release();this.captureSessionnull;}this.stateCameraState.INITIALIZED;}}设计要点用枚举明确状态流转路径避免通过布尔变量组合推导状态releaseSessionOnly()是专门为进入后台准备的轻量级释放方案保留CameraManager和CameraInput可以避免重新初始化硬件设备isInBackground标志位用于判断是否需要完整恢复还是部分恢复第二步在Page生命周期中处理暂停与恢复// CameraPage.etsimport{camera}fromkit.CameraKit;import{image}fromkit.ImageKit;import{window}fromkit.ArkUI;import{BusinessError}fromkit.BasicServicesKit;import{CameraStateManager,CameraState}from./CameraStateManager;EntryComponentstruct CameraPage{StatepreviewWidth:number0;StatepreviewHeight:number0;privatecameraStateMgr:CameraStateManagernewCameraStateManager();privatesurfaceId:string;aboutToAppear():void{// 注册窗口事件监听确保能捕获系统级暂停和恢复constwinwindow.getLastWindow(getContext());win.then(windowInfo{windowInfo.on(windowEvent,(eventName:window.WindowEventType){if(eventNamewindow.WindowEventType.WINDOW_HIDE){this.handlePause()}elseif(eventNamewindow.WindowEventType.WINDOW_SHOW){this.handleResume()}})})}onPageShow():void{// 页面从后台显示时尝试恢复相机if(this.cameraStateMgr.background){this.handleResume()}}onPageHide():void{// 页面隐藏时暂停相机this.handlePause()}onWindowStageCreate(windowStage:window.WindowStage):void{windowStage.on(windowEvent,(eventName:window.WindowEventType){if(eventNamewindow.WindowEventType.WINDOW_HIDE){this.handlePause()}elseif(eventNamewindow.WindowEventType.WINDOW_SHOW){this.handleResume()}})}aboutToDisappear():void{// 页面销毁时释放所有资源包括CameraManagerthis.cameraStateMgr.releaseAllResources()}// 暂停相机进入后台privatehandlePause():void{if(this.cameraStateMgr.stateCameraState.PREVIEW_STARTED){console.info([CameraLifecycle] 暂停相机资源)// 停止预览this.cameraStateMgr.preview?.release()// 释放Session但保留CameraManager和CameraInputthis.cameraStateMgr.releaseSessionOnly()this.cameraStateMgr.backgroundtruethis.cameraStateMgr.setState(CameraState.BACKGROUND)}}// 恢复相机切回前台privatehandleResume():void{if(!this.cameraStateMgr.background){return}console.info([CameraLifecycle] 恢复相机资源)this.cameraStateMgr.backgroundfalsethis.rebuildCameraSession()}// 重建相机会话privateasyncrebuildCameraSession():Promisevoid{try{// 1. 获取CameraManagerif(!this.cameraStateMgr.manager){this.cameraStateMgr.managercamera.getCameraManager(getContext())}// 2. 获取CameraInput保留之前选中的相机IDif(!this.cameraStateMgr.input){constcamIdsthis.cameraStateMgr.manager.getSupportedCameras()if(camIds.length0){thrownewError(未找到可用摄像头)}consttargetCamIdthis.cameraStateMgr.cameraId||camIds[0]this.cameraStateMgr.cameraInputthis.cameraStateMgr.manager.createCameraInput(targetCamId)this.cameraStateMgr.input.open()this.cameraStateMgr.cameraIdtargetCamId}// 3. 创建新的CaptureSession系统回收后必须重新创建this.cameraStateMgr.sessionthis.cameraStateMgr.manager.createCaptureSession()// 4. 创建PreviewOutputthis.cameraStateMgr.previewthis.cameraStateMgr.manager.createPreviewOutput(this.surfaceId)// 5. 开始会话this.cameraStateMgr.session.beginConfig()this.cameraStateMgr.session.addInput(this.cameraStateMgr.input)this.cameraStateMgr.session.addOutput(this.cameraStateMgr.preview)awaitthis.cameraStateMgr.session.commitConfig()awaitthis.cameraStateMgr.session.start()this.cameraStateMgr.setState(CameraState.PREVIEW_STARTED)console.info([CameraLifecycle] 相机恢复成功)}catch(error){leterrerrorasBusinessErrorconsole.error([CameraLifecycle] 恢复相机失败:${err.code}${err.message})}}build(){Stack(){if(this.previewWidth0this.previewHeight0){XComponent({id:cameraPreview,type:XComponentType.SURFACE,width:this.previewWidth,height:this.previewHeight})}}.width(100%).height(100%)}}关键决策为什么不直接用onPageShow和onPageHide因为页面回退栈管理下这两个回调不够精准叠加window.WindowEventType可以覆盖更多场景恢复时先判断background标志位避免在非后台场景下重复初始化rebuildCameraSession必须创建新的CaptureSession对象这是Camera Kit的内部机制——系统回收后旧Session句柄无效常见问题问题1恢复后预览黑屏现象应用从后台切回前台无异常抛出但预览区域为黑屏。原因最常见的是XComponent的Surface ID在页面隐藏期间被系统回收。createPreviewOutput时传入的surfaceId已经失效。解决方案在rebuildCameraSession之前获取最新的Surface ID在build()中不为XComponent绑定id时设置一个稳定ID常量避免重建时改变ID监听XComponent.onDestroy事件Surface销毁时主动释放PreviewOutput// 在rebuildCameraSession中if(!this.surfaceId){constxcomthis.findChildById(cameraPreview)asXComponentthis.surfaceIdxcom.getXComponentSurfaceId()}问题2恢复后报错“Service busy”现象commitConfig或start时抛出13900001错误。原因相机服务在后台释放资源有延迟。应用切回前台过快Camera Manager尚未完成内部状态重置。解决方案添加一个200ms的延迟重试逻辑但不要盲目加长超时避免用户感知到卡顿。privateasyncrebuildWithRetry(maxRetries:number3):Promisevoid{for(leti0;imaxRetries;i){try{awaitthis.rebuildCameraSession()return}catch(error){if(imaxRetries-1){throwerror}awaitnewPromise(resolvesetTimeout(resolve,200i*100))}}}问题3荣耀/华为部分老设备上恢复后闪退现象仅在部分设备的API 12以下版本出现camera.getCameraManager()返回null。原因Camera Kit在老设备上有权限预热策略限制应用被回收后需要重新请求权限。解决方案在aboutToAppear和handleResume中重新检查相机权限。privateasyncensureCameraPermission():Promiseboolean{constpermissionResultawaitcheckAccessToken(ohos.permission.CAMERA)if(permissionResult!1){// 提示用户授权returnfalse}returntrue}最佳实践不要在onForeground中直接重建相机会话。系统可能在同一时刻触发多次WINDOW_SHOW事件每次重建都会释放旧资源创建新资源频繁操作会导致相机服务无响应。建议加一个防抖机制或者用background标志位限制只执行一次恢复。进入后台时不要释放CameraManager。CameraManager实例可以跨生命周期复用。完全释放后恢复时需要重新建立与系统相机服务的连接增加100-300ms的额外延迟。只释放Session和Output即可。Surface ID的获取时机必须按以下顺序初始创建时在onPlace回调中获取恢复时重新获取不要在build()中每次计算Surface ID这会导致预览区域闪烁XComponent({id:cameraPreview,type:XComponentType.SURFACE}).onLoad((xcom){this.surfaceIdxcom.getXComponentSurfaceId()})FAQQ主页面恢复相机成功但再次切换到后台后恢复失败A检查handlePause中是否释放了CameraInput。如果释放了CameraInput恢复时必须重新open()。建议只释放Session和Output保留Input。Q为什么返回按钮退出页面后状态栏还显示相机图标AaboutToDisappear中调用了releaseAllResources但release()是异步操作需要等待Promise完成。建议使用await或者Promise.all等待所有资源释放完成。Q权限授权逻辑需要重新走一遍吗A需要。应用被系统回收后权限授权状态可能被重置。建议在aboutToAppear和handleResume中都调用checkAccessToken。不过如果只是切后台不销毁页面权限不会丢失。