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);
}

嗯,这里要注意:onErroronDisconnected 是两回事。

  • 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,问题就解决了。

三、错误处理的核心逻辑

错误处理不是简单的“收到错误就关闭”。你需要根据错误类型,采取不同的策略。

我一般这样处理:

  1. 收到 ERROR_CAMERA_DEVICE:立即关闭 CameraDevice,释放所有资源。然后通知用户“相机出现故障”,建议重启应用。
  2. 收到 ERROR_CAMERA_SERVICE:先关闭当前 CameraDevice,等待 1-2 秒后尝试重新打开。因为 CameraService 可能会自动恢复。
  3. 收到 ERROR_CAMERA_IN_USE:提示用户“相机被其他应用占用”,让用户手动关闭其他应用后再试。
  4. 收到 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 错误处理知识体系。你可以把它当作一个检查清单,看看自己有没有遗漏。

Camera 错误处理知识体系 错误来源 硬件层错误 服务层错误 应用层错误 ERROR_CAMERA_DEVICE 驱动崩溃 / 硬件故障 ERROR_CAMERA_SERVICE 服务进程异常 ERROR_CAMERA_IN_USE 资源被占用 释放资源 + 提示用户 指数退避重连 提示用户关闭其他应用 降级方案:拍照模式 → 低分辨率 → 系统相机

六、实战中的避坑指南

最后,分享几个我踩过的坑,希望能帮你少走弯路。

坑1:不要在 onError 回调里做耗时操作

onError 是在 Binder 线程里回调的。如果你在里面做网络请求或者文件 IO,会阻塞 Binder 线程,导致其他回调收不到。我一般只做状态标记,然后切到主线程处理。

坑2:重连时不要忘记重置 CaptureSession

我曾经只重新打开了 CameraDevice,但没重建 CaptureSession。结果预览流一直黑屏,查了半天才发现是 Session 还处于关闭状态。记住:重连 = 重新打开 Device + 重新创建 Session + 重新设置预览。

坑3:降级方案要提前准备好

不要等到出错了才去想降级方案。我习惯在初始化时,就把降级路径的代码写好。比如提前准备好低分辨率的配置参数,这样出错时可以直接切换,不用现场计算。

好了,关于 Camera 错误处理的内容,今天就聊到这里。错误处理这件事,做得好用户无感知,做不好就是闪退和黑屏。希望今天的分享,能帮你把相机做得更稳。


公众号:蓝海资料掘金营,微信deep3321