折叠形态一变相机就黑屏?别只重排预览框,还要重选设备和会话世代
折叠屏从展开态切到单屏态后,页面布局已经重排,预览却停在最后一帧;再切回来,有时画面旋转 90 度。问题通常不在预览组件,而在应用继续沿用形态变化前的相机设备、输出尺寸和旧会话回调。官方多设备适配指南把“形态切换后相机画面连续”单独列为风险,说明这不是简单的 UI 缩放。
验证边界:本文核对了华为开发者官网截至 2026-09-24 可访问的资料。官方事实与文中的纯函数示例分开说明:示例逻辑已在 Node.js 宿主环境运行,当前本机仍是 API 24 SDK 且没有连接 HDC 真机,因此不把示例称为 API 26 编译或真机实测;正式交付必须在 API 26 SDK、目标设备或对应模拟器上补齐验证。
先看事故现场
形态切换可能改变可用摄像头、显示方向和预览区域。若应用只监听窗口尺寸,把旧 surface 裁成新比例,旧会话仍可能向已经失效的目标输出;如果异步重建期间用户再次折叠,第一次重建的回调又会覆盖第二次结果。表现就是黑屏、拉伸、方向错误或偶发双会话占用。
根因不是一个布尔值
把每次形态变化转换成相机配置请求:形态快照、目标朝向、预览尺寸和递增世代。先停止旧输出,再按当前设备列表选择相机,创建新会话并等待首帧;只有首帧属于当前世代时才切换 UI。失败时保留明确占位和重试入口,而不是展示冻结画面。
interfaceCameraPlan{generation:number;cameraId:string;width:number;height:number;rotation:number}exportfunctionisCurrent(plan:CameraPlan,generation:number):boolean{returnplan.generation===generation;}exportfunctionchoosePreview(boxW:number,boxH:number,candidates:Array<[number,number]>):[number,number]{consttarget=boxW/boxH;returncandidates.reduce((best,size)=>Math.abs(size[0]/size[1]-target)<Math.abs(best[0]/best[1]-target)?size:best);}复现一:展开态前摄切到单屏态不可用
不要继续复用原 cameraId。收到形态变化后重新读取可用设备,按朝向和业务要求选取候选;找不到等价设备时进入降级页面,告诉用户当前形态不支持,而不是不断重开旧相机。
复现二:连续折叠两次,第一次重建最后才返回
每次重建携带世代号。第一次会话即使创建成功,只要世代已经落后,就立刻释放而不绑定预览;第二次首帧到达后再替换占位图。这个规则能同时消除“旧画面复活”和资源泄漏。
修复后的证据链
| 观察项 | 容易误判 | 可复核做法 |
|---|---|---|
| 设备选择 | 缓存 cameraId 永久复用 | 形态变化后重读设备并验证朝向 |
| 预览比例 | 用旧尺寸直接拉伸 | 按新窗口比例重新选择输出尺寸 |
| 并发重建 | 谁最后返回谁生效 | 只接受当前 generation 的会话 |
| 失败显示 | 停在最后一帧 | 明确占位、错误原因与重试 |
先验证设备可用性,再重建会话,最后切换 UI。这样比“尺寸变化就重建全部页面”更容易控制资源,也能把相机错误与布局错误分开定位。
上线前别漏这几项
- 形态切换后重新确认 cameraId 可用。
- 方向与预览比例按新窗口计算。
- 旧世代会话完成后立即释放。
- 首帧到达前显示明确占位。
- 失败路径不会形成无限重试。
官方资料
- 多设备通用适配指南
- 三折叠应用开发
相机连续性不是“预览框还在”就算完成。设备、会话、方向和首帧必须属于同一个形态世代,画面才真正连续。