ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

HarmonyOS 7 CameraManager:折叠态预览重建与会话交接

HarmonyOS 7 CameraManager:折叠态预览重建与会话交接 折叠屏相机页面有一种很典型的“偶尔卡死”展开、折叠之后布局已经变了按钮也能点预览却停在上一帧。重新进入页面又正常。真正麻烦的不是重建预览本身而是两个信号几乎同时到来——CameraManager 的foldStatusChange告诉应用可用相机集合与折叠状态变化窗口回调又告诉页面 Surface 尺寸变化。若两个回调都各自释放、创建、启动会话第二次操作很容易踩进第一次尚未结束的资源交接。本文用演示项目FoldPreviewRelay拆解这个问题。固定任务为CAM-1317-624页面FoldCameraPage13:17 从 FOLDED 切到 EXPANDED目标 Surface 为1096×1812会话代次由 11 切到 12。演示快照停在 17/25也就是 68%当前状态WAIT_SURFACE_STABLE两次窗口事件被合并迟到回调丢弃 1 次。图与数字属于设计演示不冒充真机跑通记录。一、先把两个事件的职责分开foldStatusChange的价值在于相机侧信息。官方 CameraManager 声明的回调返回FoldStatusInfo其中包含foldStatus与supportedCameras。因此它适合触发“候选相机是否变化”的判断。窗口尺寸变化则更适合回答 Surface 几何是否稳定、预览比例是否需要重算。两者都只是输入不应该直接拥有会话生命周期。官方多设备适配资料也把问题分成两个层面折叠、旋转或切屏后要重新校正方向与预览比例形态切换过程中要监听折叠状态并动态重选相机。这里没有承诺事件顺序也没有应用层的去抖事务。工程上必须假设窗口事件可能连续到达、Surface ID 可能尚未准备好、相机会话的 stop 与 release 仍在异步完成。FoldPreviewRelay因此只让事件处理器更新“目标快照”不碰旧会话。快照包含 foldStatus、候选 cameraId、surfaceId、宽高、方向与事件序号。调度器读取最新快照等窗口稳定后串行执行一次交接执行期间若又有新快照只设置dirtytrue当前交接结束后再按最新值补跑一次。这种结构看起来比在回调里直接restartPreview()多了一层实际减少了三类隐式状态谁正在释放、谁最后写入 Surface、哪个回调属于旧会话。页面只展示协调器状态不再从多个 Promise 分别改同一个State。二、监听要成对回调只写入事实第一段代码解决监听注册与注销。回调对象被保存为字段off时传入同一引用页面离开后通过viewEpoch让已排队任务失效。supportedCameras不直接取第一个而是交给业务选择器按镜头位置、类型与能力选择示例省略选择细节避免把数组顺序写成平台保证。import{camera}fromkit.CameraKit;interfaceFoldTarget{foldStatus:camera.FoldStatus;cameras:ReadonlyArraycamera.CameraDevice;seq:number;}exportclassFoldEventRelay{privateseq:number0;privateactive:booleanfalse;constructor(privatemanager:camera.CameraManager,privateonTarget:(target:FoldTarget)void){}privatereadonlycallback(info:camera.FoldStatusInfo):void{if(!this.active)return;this.seq1;this.onTarget({foldStatus:info.foldStatus,cameras:[...info.supportedCameras],seq:this.seq});};start():void{if(this.active)return;this.activetrue;this.manager.on(foldStatusChange,this.callback);}stop():void{if(!this.active)return;this.activefalse;this.manager.off(foldStatusChange,this.callback);}}active不是用来替代off而是给迟到回调加第二道保险。生命周期必须同时具备“停止来源”和“拒绝旧消息”。实际页面在aboutToDisappear里先停止接收再要求协调器完成或取消后续调度不能先释放会话、稍后才关监听否则新折叠事件可能再次启动创建。三、窗口稳定不是固定睡眠直接延迟 300 毫秒的问题很明显设备快时浪费慢时仍然太早。本文使用“连续两次采样一致”作为应用层稳定条件。窗口每次变化都覆盖 width、height、surfaceId 与 revision。调度器在下一帧或短周期采样中比较 revision相同尺寸连续出现两次才允许进入相机会话配置。演示中窗口先上报1080×1812随后修正为1096×1812两个变化合并为一个目标。第 17/25 步时状态仍是WAIT_SURFACE_STABLE所以图中 68% 并不代表系统接口提供进度而是应用工作流计数。若 Surface 被销毁或 ID 为空协调器保持等待不创建 PreviewOutput。第二段代码给出事件合流器。它不依赖不存在的系统事务而是应用内的单消费者队列。request()可以被折叠回调、窗口回调和页面恢复共同调用drain()同时只能有一个实例运行。interfacePreviewSnapshot{fold:string;cameraId:string;surfaceId:string;width:number;height:number;revision:number;}exportclassPreviewRebuildQueue{privatelatest?:PreviewSnapshot;privaterunning:booleanfalse;privatedirty:booleanfalse;privategeneration:number11;constructor(privaterebuild:(s:PreviewSnapshot,gen:number)Promisevoid){}request(snapshot:PreviewSnapshot):void{this.latestsnapshot;this.dirtytrue;if(!this.running)voidthis.drain();}privateasyncdrain():Promisevoid{this.runningtrue;try{while(this.dirtythis.latest){this.dirtyfalse;consttarget{...this.latest};this.generation1;awaitthis.rebuild(target,this.generation);}}finally{this.runningfalse;}}}循环不是无限重试。只有新快照到来才重新运行重建抛错时应进入FAILED并退出由显式恢复动作再次 request。若捕获异常后无条件继续循环摄像头服务异常会变成高频创建风暴。日志必须记录 target revision、generation 与失败阶段。项目结构与调试界面如下。左侧是FoldEventRelay.ets、SurfaceStableGate.ets、PreviewRebuildQueue.ets和页面文件中间显示合流与代次判断右侧模拟器停在WAIT_SURFACE_STABLE · 68%底部 HiLog 使用同一任务CAM-1317-624、Surface1096×1812、代次 11→12、merged2、lateDropped1。该图是白色主题演示图不是 DevEco Studio 实测截图。四、旧会话要先停再释放最后才替换引用Camera Session 保存 CameraInput 与 CameraOutput。官方接口提供beginConfig()、addInput()、addOutput()、commitConfig()、start()、stop()与release()。构建新会话前应用应根据目标相机重新查询能力与预览 Profile并让预览 Profile 和 Surface 保持相同比例。不能沿用折叠前的 Profile 假定新几何仍兼容。第三段代码以PreviewResources聚合会话、输入和输出。清理顺序写在一个函数里并用代次决定新资源能否成为 active。不同 API 的同步/异步签名应以项目使用的 SDK 声明为准示例只采用官方确认的会话配置顺序业务的 Profile 选择和 Surface 创建留给封装层。import{camera}fromkit.CameraKit;interfacePreviewResources{session:camera.Session;input:camera.CameraInput;output:camera.PreviewOutput;generation:number;}asyncfunctioncloseResources(old?:PreviewResources):Promisevoid{if(!old)return;try{awaitold.session.stop();}finally{awaitold.session.release();awaitold.input.close();awaitold.output.release();}}asyncfunctionactivate(manager:camera.CameraManager,device:camera.CameraDevice,profile:camera.Profile,surfaceId:string,generation:number,isCurrent:(g:number)boolean):PromisePreviewResources|undefined{constinputmanager.createCameraInput(device);constoutputmanager.createPreviewOutput(profile,surfaceId);constsessionmanager.createSession(camera.SceneMode.NORMAL_PHOTO);awaitinput.open();session.beginConfig();session.addInput(input);session.addOutput(output);awaitsession.commitConfig();awaitsession.start();constbuilt{session,input,output,generation};if(!isCurrent(generation)){awaitcloseResources(built);returnundefined;}returnbuilt;}风险集中在异常路径。input 已打开但 session 配置失败时也要关闭 input 和 outputcommit 成功但 start 失败时session 仍需 release。实际封装最好用逐阶段标志或资源栈不要只在成功对象生成后才清理。旧 active 引用要等closeResources完成后再清空避免另一个路径把半释放对象当作可用会话。五、页面展示的是协调器状态不是相机真相的猜测13:17 的运行页显示FOLDED→EXPANDEDSurface1096×1812会话 generation 1217/25、68%状态WAIT_SURFACE_STABLE。顶部完整显示时间、Wi‑Fi、5G、信号和 76% 电量无手机外壳。红色箭头强调“等待几何稳定”避免读者把 68% 理解为相机启动进度。详情页则展示事件时间线foldStatusChange 到达、windowSizeChange 两次、合并计数 2、旧会话 stop/release、generation 11→12、lateDropped1。它和首页不是同一布局承担的是解释串行交接的作用。六、调试时优先核对五条证据第一是否出现两个并行 rebuild。给每次 drain 分配 traceId若同一页面同时存在两个 active trace合流器失效。第二Surface ID 与宽高是否来自同一 revision。把旧 ID 与新尺寸拼在一起比例计算再准确也会黑屏。第三Profile 是否针对目标 camera 重新选择。supportedCameras变化不是 UI 提示它意味着原设备选择可能失效。第四旧资源是否完整成对关闭。仅 stop 不 release 会留下会话只 release session 而不处理 input/output 也会增加后续失败的不确定性。第五frameStart 是否属于当前 generation。官方预览输出可以监听帧开始和错误收到帧并不代表它一定来自当前 UI 想要的会话。日志要带 generation旧代次事件只计数不改页面。本文没有给出“折叠后必定在多少毫秒恢复”的数字因为那需要真机、镜头、Profile、温度和负载测量。演示中的 68%、事件时间与丢弃计数只是用来证明状态关系。真实验收应覆盖展开到折叠、折叠到展开、快速往返、后台回前台、Surface 重建失败和权限撤回。还要单独验证拍照与录像并存的页面。预览重建不一定意味着拍照任务可以无条件终止若快门已经提交业务应先冻结形态切换入口或把拍照结果交给旧 generation 完成后再切换。直接在 foldStatusChange 中释放所有输出可能让用户看到“已按下快门却没有照片”。录像更谨慎正在录制时是否允许换相机、是否先停止录制、如何提示文件分段都应是产品明确决策不能由通用预览协调器偷偷处理。权限变化也要进入状态机。页面从后台回来时相机权限可能已被撤销。此时 RESUME 事件不能只 request 最新快照而要先重新核对权限没有权限就进入PERMISSION_REQUIRED关闭残留资源并禁止创建 input。把权限错误混成FAILED会让恢复按钮不断重试相机服务用户却看不到真正原因。性能测量应围绕阶段而不是只看最终首帧。可以分别记录 oldSession.stop、oldSession.release、input.open、commitConfig、session.start 和 frameStart 的耗时再按 generation 聚合。只有这样慢在资源回收还是慢在新相机首帧才说得清。日志中的任务 ID、形态、Surface revision 与 cameraId 必须齐全但不要写入不必要的用户内容。无障碍与旋转也不能遗漏。重建期间按钮应明确禁用原因读屏焦点不要因整个页面重建而跳回顶部方向校正完成前可以显示静态占位而不是拉伸上一帧。折叠屏适配最终仍是完整页面体验不能因为相机链路复杂就牺牲交互可解释性。七、参考边界CameraManager 的foldStatusChange、FoldStatusInfo.foldStatus与supportedCameras依据当前 Camera Kit API 声明。Camera Session 的 beginConfig/commitConfig、输入输出配置、start/stop/release 顺序依据华为当前 API 文档。预览 Surface 与 Preview Profile 保持相同比例、折叠形态切换后重选相机与重建预览流依据相机预览和多设备适配指南。相机折叠适配不是“收到折叠事件就重启一次”。更可靠的实现是让折叠事件决定目标相机让窗口事件决定目标几何再由唯一协调器串行完成资源交接。事件可以乱序目标可以覆盖旧回调可以迟到只要 generation、revision 和资源所有权清楚页面就不需要靠退出重进恢复。
返回列表