23、Camera 错误处理:CameraErrorCallback、ErrorCode详解
做Camera开发这么多年,我踩过最多的坑,就是错误处理。
你想想看,相机是个硬件设备,它不像普通软件那样稳定。驱动崩了、服务挂了、内存不够了,任何一个环节出问题,你的App就得跟着遭殃。我见过太多App在相机出错时直接闪退,或者卡死在那不动——说白了,就是没做好错误处理。
今天我们就来聊聊Android Camera框架里的错误处理机制。我会把ErrorCallback、ErrorCode、重连策略这些东西,掰开了讲清楚。
一、CameraErrorCallback 是什么?
CameraErrorCallback,是相机框架给上层App的一个“救命通道”。
当相机底层发生异常时,系统会通过这个回调通知你。你收到通知后,可以做相应的处理——比如释放资源、提示用户、或者尝试重连。
核心接口定义:
public interface CameraDevice.StateCallback {
void onOpened(CameraDevice camera);
void onDisconnected(CameraDevice camera);
void onError(CameraDevice camera, int error);
}
嗯,这里要注意:onError 和 onDisconnected 是两回事。
- onDisconnected:相机被其他更高优先级的应用抢走了,或者系统主动断开了连接。这种情况通常可以重试。
- onError:相机发生了严重错误,比如硬件故障、驱动崩溃。这种情况往往需要释放资源,不能直接重试。
我个人习惯,在 onError 里先调用 camera.close(),再根据 error code 决定下一步动作。
二、ErrorCode 详解
Android 定义了多个错误码,每个码代表一类问题。我整理了一张表,方便你对照查阅。
| ErrorCode | 常量名 | 含义 | 常见原因 |
|---|---|---|---|
| 1 | ERROR_CAMERA_DEVICE | 相机设备发生致命错误 | 驱动崩溃、硬件故障、DMA传输失败 |
| 2 | ERROR_CAMERA_SERVICE | CameraService 内部错误 | 服务进程崩溃、权限校验失败 |
| 3 | ERROR_CAMERA_DISABLED | 相机被系统禁用 | 设备策略限制、安全模式 |
| 4 | ERROR_CAMERA_IN_USE | 相机已被其他应用占用 | 多应用同时打开相机 |
| 5 | ERROR_MAX_CAMERAS_IN_USE | 同时打开的相机数量超限 | 同时打开前后摄 + 其他摄像头 |
注意:ERROR_CAMERA_DEVICE 和 ERROR_CAMERA_SERVICE 是最常见的两个错误。前者是设备级问题,后者是服务级问题。处理方式完全不同。
我曾经在一个项目中,连续收到 ERROR_CAMERA_SERVICE。查了半天,发现是 CameraService 的 Binder 线程池满了,导致回调超时。后来我们加了一个重试机制,每次重试前等待 500ms,问题就解决了。
三、错误处理的核心逻辑
错误处理不是简单的“收到错误就关闭”。你需要根据错误类型,采取不同的策略。
我一般这样处理:
- 收到 ERROR_CAMERA_DEVICE:立即关闭 CameraDevice,释放所有资源。然后通知用户“相机出现故障”,建议重启应用。
- 收到 ERROR_CAMERA_SERVICE:先关闭当前 CameraDevice,等待 1-2 秒后尝试重新打开。因为 CameraService 可能会自动恢复。
- 收到 ERROR_CAMERA_IN_USE:提示用户“相机被其他应用占用”,让用户手动关闭其他应用后再试。
- 收到 ERROR_CAMERA_DISABLED:这种情况通常是系统策略限制,比如企业设备管理。建议直接跳转到系统设置页面。
我的经验:不要在所有错误发生后都立即重试。我曾经犯过这个错——在 ERROR_CAMERA_DEVICE 后立即重试,结果导致相机驱动反复崩溃,最后系统直接禁用了相机硬件。重启手机才恢复。
四、重连策略与降级方案
重连策略,说白了就是“相机挂了之后,怎么优雅地恢复”。
我推荐一个“指数退避 + 最大重试次数”的策略:
private int retryCount = 0;
private static final int MAX_RETRY = 3;
private static final long BASE_DELAY_MS = 1000;
private void reopenCamera() {
if (retryCount >= MAX_RETRY) {
// 重试次数超限,走降级方案
degradeToPhotoOnly();
return;
}
long delay = BASE_DELAY_MS * (long) Math.pow(2, retryCount);
retryCount++;
new Handler().postDelayed(() -> {
openCamera(cameraId, stateCallback, backgroundHandler);
}, delay);
}
为什么要用指数退避?你想想看,如果相机服务刚崩溃,你立刻重试,大概率还是失败。等几秒再试,成功率会高很多。
降级方案,是重连失败后的最后一道防线。我常用的降级策略有:
- 降级到拍照模式:如果预览流无法建立,就只使用 CameraCaptureSession 的单帧拍照功能。
- 降级到低分辨率:如果高分辨率模式报错,就尝试 640x480 这种基础分辨率。
- 降级到系统相机:如果自己的相机模块完全不可用,就通过 Intent 调用系统相机 App。
核心原则:降级不是放弃,而是用有限的功能,保证用户体验不中断。
五、知识体系总览
下面这张图,是我梳理的 Camera 错误处理知识体系。你可以把它当作一个检查清单,看看自己有没有遗漏。
六、实战中的避坑指南
最后,分享几个我踩过的坑,希望能帮你少走弯路。
坑1:不要在 onError 回调里做耗时操作
onError 是在 Binder 线程里回调的。如果你在里面做网络请求或者文件 IO,会阻塞 Binder 线程,导致其他回调收不到。我一般只做状态标记,然后切到主线程处理。
坑2:重连时不要忘记重置 CaptureSession
我曾经只重新打开了 CameraDevice,但没重建 CaptureSession。结果预览流一直黑屏,查了半天才发现是 Session 还处于关闭状态。记住:重连 = 重新打开 Device + 重新创建 Session + 重新设置预览。
坑3:降级方案要提前准备好
不要等到出错了才去想降级方案。我习惯在初始化时,就把降级路径的代码写好。比如提前准备好低分辨率的配置参数,这样出错时可以直接切换,不用现场计算。
好了,关于 Camera 错误处理的内容,今天就聊到这里。错误处理这件事,做得好用户无感知,做不好就是闪退和黑屏。希望今天的分享,能帮你把相机做得更稳。
公众号:蓝海资料掘金营,微信deep3321