29. Camera兼容性处理:不同厂商适配、Android版本差异、常见问题排查

做Android摄像头开发,最头疼的是什么?

不是API不会用,而是同一套代码,在小米上跑得好好的,换到华为就黑屏了。换到OPPO,直接闪退。我刚开始做这块时,被这种问题折磨得够呛。后来慢慢摸清了门道,今天就把这些经验掰开揉碎讲给你听。

核心观点:Camera兼容性问题的本质,是厂商定制ROM + 硬件差异 + Android版本碎片化三者叠加的结果。没有银弹,但有套路。

29.1 厂商适配:那些年我们踩过的坑

国内厂商对Camera HAL层的修改,可以说是「八仙过海,各显神通」。我整理了一份常见问题清单,你看看是不是很眼熟:

厂商 常见问题 我的解决经验
华为/荣耀 Camera.open() 返回null,或预览画面旋转90度 华为部分机型需要先检查Camera.getNumberOfCameras(),再逐个尝试open
小米 预览画面卡顿,或拍照后图片方向不对 小米的Camera1 API在部分MIUI版本中,setDisplayOrientation无效,需要手动旋转
OPPO/vivo 后台Camera被系统回收,或权限弹窗异常 这两家对后台Camera限制极严,建议onPause时务必release
三星 Camera.open() 耗时过长,或预览尺寸不支持 三星的Camera HAL层实现比较「标准」,但部分低端机型支持的预览尺寸很少

我的小技巧:在应用启动时,先跑一个「兼容性检测」流程。遍历所有Camera ID,尝试open、setParameters、startPreview,如果失败就记录日志并降级处理。这样用户至少能看到错误提示,而不是直接闪退。

29.2 Android版本差异:从Camera1到CameraX的进化

Android版本对Camera的影响,说白了就是API的变迁史。我经历过从Camera1到Camera2,再到CameraX的全过程,每个阶段都有坑。

29.2.1 Camera1 API(API 9-28)

这个API虽然老,但兼容性最好。几乎所有Android 5.0以下的设备都靠它。不过有个致命问题:不支持多摄像头

我记得有一次,用户反馈说「为什么我的手机有两个摄像头,但App只能用主摄?」嗯,这就是Camera1的局限。它只能操作一个Camera对象,没法同时管理前后摄。

29.2.2 Camera2 API(API 21+)

Camera2是Google力推的,功能强大,但兼容性一言难尽。我遇到过最离谱的情况:某款手机明明支持Camera2,但调用createCaptureSession时直接抛IllegalStateException。

// Camera2兼容性检查代码
private boolean isCamera2Supported() {
    try {
        CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        String[] cameraIds = manager.getCameraIdList();
        for (String id : cameraIds) {
            CameraCharacteristics chars = manager.getCameraCharacteristics(id);
            Integer level = chars.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
            // 注意:有些厂商返回的是INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
            // 但实际上支持部分Camera2功能
            if (level != null && level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
                Log.w("CameraCompat", "Camera " + id + " is LEGACY mode");
                // 这里可以降级到Camera1
            }
        }
        return true;
    } catch (Exception e) {
        return false;
    }
}

注意:不要完全依赖INFO_SUPPORTED_HARDWARE_LEVEL来判断。有些厂商的「LEGACY」模式其实能跑Camera2的大部分功能,只是个别特性不支持。我建议你实际调用一下关键API,看会不会抛异常。

29.2.3 CameraX(API 21+,Jetpack)

CameraX是Google的「救火队员」。它封装了Camera2的复杂性,自动处理了大部分兼容性问题。我个人现在做新项目,首选CameraX。

但CameraX也不是万能的。比如它不支持手动对焦的精细控制,也不支持RAW格式输出。如果你需要这些功能,还是得用Camera2。

29.3 常见问题排查:我的「三板斧」

遇到Camera兼容性问题,别慌。我有一套固定的排查流程,90%的问题都能定位到根因。

  1. 第一板斧:检查权限
    • CAMERA权限是否在Manifest中声明?
    • 运行时权限是否已授予?
    • 部分厂商(如华为)在Android 10以上,还需要检查「后台弹出界面」权限
  2. 第二板斧:检查生命周期
    • Camera对象是否在onResume中打开,在onPause中释放?
    • 有没有在子线程中操作Camera?Camera API不是线程安全的
    • SurfaceView/TextureView的surfaceCreated回调是否在Camera.open之后?
  3. 第三板斧:检查参数
    • 预览尺寸是否在getSupportedPreviewSizes列表中?
    • 图片格式是否支持?大部分设备只支持NV21和YV12
    • 帧率是否在合理范围?不要设置30fps以上,很多设备不支持

我曾经遇到一个案例:某款手机在Camera.open()后,调用setParameters就崩溃。查了半天,发现是预览尺寸设置成了1920x1080,但该手机只支持1280x720。所以,永远不要硬编码预览尺寸,一定要从getSupportedPreviewSizes中选一个。

29.4 知识体系:一张图看懂Camera兼容性

下面这张图,是我自己总结的Camera兼容性处理框架。每次遇到新问题,我都会对照这张图来排查。

Camera兼容性处理框架 厂商定制ROM 硬件差异 Android版本碎片化 核心问题:API调用失败 / 预览异常 / 性能问题 第一板斧:检查权限 第二板斧:检查生命周期 第三板斧:检查参数 解决方案:降级到Camera1 / 使用CameraX / 厂商特定适配 图:Camera兼容性处理框架

29.5 实战:一个通用的Camera兼容性工具类

说了这么多理论,来点实际的。下面这个工具类,是我在多个项目中打磨出来的,专门处理Camera兼容性问题。

public class CameraCompatUtils {
    
    /**
     * 安全打开Camera,处理各种异常
     */
    public static Camera safeOpenCamera(int cameraId) {
        try {
            return Camera.open(cameraId);
        } catch (Exception e) {
            // 有些厂商在Camera.open()时可能抛RuntimeException
            Log.e("CameraCompat", "open camera failed: " + e.getMessage());
            // 尝试降级:用Camera.open()无参版本
            try {
                return Camera.open();
            } catch (Exception e2) {
                return null;
            }
        }
    }
    
    /**
     * 获取最佳预览尺寸
     */
    public static Camera.Size getBestPreviewSize(Camera.Parameters params, int targetWidth, int targetHeight) {
        List<Camera.Size> sizes = params.getSupportedPreviewSizes();
        if (sizes == null || sizes.isEmpty()) {
            return null;
        }
        
        // 优先选择与目标尺寸最接近的
        Camera.Size bestSize = sizes.get(0);
        int bestDiff = Integer.MAX_VALUE;
        for (Camera.Size size : sizes) {
            int diff = Math.abs(size.width - targetWidth) + Math.abs(size.height - targetHeight);
            if (diff < bestDiff) {
                bestDiff = diff;
                bestSize = size;
            }
        }
        return bestSize;
    }
    
    /**
     * 安全设置预览方向
     */
    public static void safeSetDisplayOrientation(Camera camera, int degrees) {
        try {
            camera.setDisplayOrientation(degrees);
        } catch (Exception e) {
            // 部分小米机型会抛异常,忽略即可
            Log.w("CameraCompat", "setDisplayOrientation failed: " + e.getMessage());
        }
    }
}

使用建议:把这个工具类放在你的项目里,所有Camera操作都通过它来执行。这样即使遇到兼容性问题,也能保证App不崩溃,最多是功能降级。

29.6 避坑指南:我的血泪史

最后,分享几个我亲身踩过的坑,希望你别再走一遍。

  • 坑1:在Android 6.0以下设备上,Camera.open()可能返回null,但不会抛异常。所以一定要判空。
  • 坑2:部分华为手机在调用Camera.setParameters()时,如果参数列表中有不支持的项,会直接抛异常。建议逐个参数设置,并try-catch。
  • 坑3:三星手机在横竖屏切换时,Camera对象会被系统重置。记得在onConfigurationChanged中重新初始化Camera。
  • 坑4:不要相信getSupportedPictureSizes返回的所有尺寸。有些尺寸虽然列出来了,但实际拍照时会OOM。我建议限制最大图片尺寸不超过1920x1080。

嗯,Camera兼容性处理就是这么回事。说白了,就是「见招拆招」。你遇到的每个问题,大概率别人也遇到过。多查查StackOverflow,多看看厂商的开发者文档,慢慢就能摸清规律了。


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