13. 前后摄像头切换:检测摄像头数量、切换CameraDevice、保持预览连续性

摄像头切换,这个功能听起来简单吧?不就是关掉一个,再开另一个嘛。但真做起来,坑还挺多的。我早期做的一个项目,切换摄像头时画面会黑一下,用户反馈说「这体验也太糙了」。嗯,后来我花了不少精力才把这个问题彻底解决。

今天我们就来聊聊,怎么优雅地实现前后摄像头切换。核心就三件事:检测摄像头数量、切换CameraDevice、保持预览连续性

13.1 检测摄像头数量

你想想看,如果设备只有一个摄像头,你硬要切到后置,那不就崩了吗?所以第一步,先搞清楚设备上有几个摄像头,分别是什么朝向。

Android 提供了 CameraManager.getCameraIdList() 来获取所有摄像头 ID。然后通过 CameraCharacteristics 查询朝向信息。

// 获取摄像头管理器
CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);

// 获取所有摄像头 ID
String[] cameraIdList = cameraManager.getCameraIdList();

// 遍历,找出前置和后置
String frontCameraId = null;
String backCameraId = null;

for (String cameraId : cameraIdList) {
    CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
    Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);

    if (facing != null) {
        if (facing == CameraCharacteristics.LENS_FACING_FRONT) {
            frontCameraId = cameraId;
        } else if (facing == CameraCharacteristics.LENS_FACING_BACK) {
            backCameraId = cameraId;
        }
    }
}
小提示:有些设备可能有多个后置摄像头(比如广角+长焦),但朝向都是 BACK。如果你只关心「前后」,那用 LENS_FACING 判断就够了。

13.2 切换 CameraDevice 的核心流程

切换摄像头,说白了就是:关闭当前 CameraDevice → 打开目标 CameraDevice → 重新创建 CaptureSession。但这里有个关键点——预览的 Surface 要不要重建?

我个人习惯是:复用 TextureView 的 Surface。只要 Surface 不销毁,预览就能无缝衔接。但要注意,前后摄像头的分辨率可能不同,所以你需要重新配置一下。

13.2.1 关闭旧摄像头

private void closeCamera() {
    if (mCaptureSession != null) {
        mCaptureSession.close();
        mCaptureSession = null;
    }
    if (mCameraDevice != null) {
        mCameraDevice.close();
        mCameraDevice = null;
    }
}

这里有个细节:close() 是异步的,但 Android 保证关闭完成后,你再打开新的摄像头是安全的。我一般会在 onClosed 回调里做下一步操作,但实际项目中,直接顺序调用也没问题。

13.2.2 打开新摄像头

private void openCamera(String cameraId) {
    CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
    try {
        cameraManager.openCamera(cameraId, new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice camera) {
                mCameraDevice = camera;
                // 创建 CaptureSession
                createCaptureSession();
            }

            @Override
            public void onDisconnected(@NonNull CameraDevice camera) {
                camera.close();
            }

            @Override
            public void onError(@NonNull CameraDevice camera, int error) {
                camera.close();
            }
        }, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}
注意:openCamera 必须在主线程调用,否则会抛异常。另外,记得在 Android 6.0+ 检查相机权限。

13.3 保持预览连续性

这是最头疼的部分。切换摄像头时,画面会短暂中断。怎么让这个中断「无感」?

我的做法是:在切换期间,保持 TextureView 的 Surface 不变,并且用最后一帧画面「冻结」住。等新摄像头准备好后,再恢复预览。

具体来说:

  1. 切换前,调用 TextureView.getBitmap() 保存当前帧(可选)。
  2. 关闭旧摄像头,但不销毁 TextureView
  3. 打开新摄像头,使用同一个 Surface 创建 CaptureSession。
  4. 新 Session 创建成功后,开始预览。

这样用户看到的只是「画面停顿了一下」,而不是黑屏或闪白。

private void switchCamera() {
    // 1. 保存当前帧(可选)
    // Bitmap lastFrame = mTextureView.getBitmap();

    // 2. 关闭旧摄像头
    closeCamera();

    // 3. 切换摄像头 ID
    String newCameraId = isFrontCamera ? backCameraId : frontCameraId;
    isFrontCamera = !isFrontCamera;

    // 4. 打开新摄像头
    openCamera(newCameraId);
}
关键点:TextureView 的 Surface 在切换过程中不要调用 onSurfaceTextureDestroyed 返回 true。否则 Surface 会被销毁,你就得重建了。

13.4 处理预览方向

前后摄像头的传感器方向可能不同。前置摄像头通常是镜像的,后置是正常的。如果你不做处理,画面可能会「倒立」或「左右翻转」。

我一般用 CameraCharacteristics.SENSOR_ORIENTATION 来获取旋转角度,然后配合 TextureView.setTransform() 做校正。

private void configureTransform(int viewWidth, int viewHeight) {
    CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(mCameraId);
    Integer sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);

    // 计算旋转矩阵
    Matrix matrix = new Matrix();
    // ... 根据 sensorOrientation 和 view 尺寸计算
    mTextureView.setTransform(matrix);
}
避坑指南:我曾经遇到过前置摄像头预览画面「上下颠倒」的问题。后来发现是 sensorOrientation 是 270°,而我只考虑了 90° 的情况。所以一定要根据实际值做适配。

13.5 整体流程图

下面这张图总结了前后摄像头切换的完整流程。你可以看到,核心就是「关闭旧 → 打开新 → 复用 Surface → 恢复预览」这个闭环。

用户点击切换按钮 检测摄像头数量 & 获取目标 ID 关闭旧 CameraDevice & CaptureSession 关键:保持 TextureView Surface 不变 不销毁 TextureView,复用同一 Surface 打开新 CameraDevice 创建新 CaptureSession,开始预览

13.6 常见问题与避坑

  • 切换时黑屏:最常见的原因是 Surface 被销毁了。检查 onSurfaceTextureDestroyed 是否返回了 true。
  • 画面闪烁:可能是新旧 Session 切换时,预览请求没有及时停止。建议在关闭旧 Session 前,先停止重复请求。
  • 分辨率不匹配:前后摄像头的支持分辨率可能不同。切换后要重新选择最优尺寸,否则画面可能变形。
我曾经踩过的坑:有一次切换摄像头后,画面一直黑着,但 log 显示 Session 创建成功了。查了半天,发现是 TextureView 的 Surface 在后台被系统回收了。后来我改用 SurfaceView 才稳定。如果你遇到类似问题,可以试试换 SurfaceView。

13.7 小结

前后摄像头切换,说白了就是「关旧开新」四个字。但要做到无缝体验,你得注意三点:

  • 先检测摄像头数量,别硬切。
  • 复用 Surface,避免黑屏。
  • 处理好方向校正,画面别歪。

嗯,掌握了这些,你的摄像头切换功能就能做到「润物细无声」了。

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