You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android相机切换时出现java.lang.IllegalArgumentException问题求助

CameraX 崩溃排查:Provided camera selector unable to resolve a camera for the given use case

问题描述

在Fragment的View创建时启动相机,通过按钮实现相机切换与拍照功能,本地测试无异常,但Firebase Crashlytics显示多台设备出现崩溃,错误信息为:

java.lang.IllegalArgumentException - Provided camera selector unable to resolve a camera for the given use case

可能原因与修复方案

1. 相机选择逻辑存在漏洞

直接用==比较CameraSelector对象,虽官方默认的前后置相机选择器是单例,但部分设备存在多摄像头场景时,可能导致判断失效;同时未提前校验设备是否存在目标摄像头,切换时会选中不存在的相机。

修复代码:

private fun invertCamera() {
    val hasFrontCamera = cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) == true
    val hasBackCamera = cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) == true

    cameraSelector = when {
        cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA && hasFrontCamera -> 
            CameraSelector.DEFAULT_FRONT_CAMERA
        cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA && hasBackCamera -> 
            CameraSelector.DEFAULT_BACK_CAMERA
        else -> cameraSelector
    }
    startCamera()
}

同时在startCamera初始化阶段,先校验设备可用摄像头,避免初始选择不存在的相机:

// 在startCamera的listener内部,初始化cameraSelector前执行
cameraSelector = when {
    cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) == true -> 
        CameraSelector.DEFAULT_BACK_CAMERA
    cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) == true -> 
        CameraSelector.DEFAULT_FRONT_CAMERA
    else -> {
        Log.e("CameraError", "No available camera found")
        return@addListener
    }
}

2. UseCase绑定逻辑混乱

startCamera仅绑定Preview,captureImage中又单独绑定ImageCapture,重复绑定会引发相机资源冲突;切换相机时也未正确清理所有已绑定的UseCase。

修复代码:
在startCamera中同时绑定Preview和ImageCapture,避免重复绑定:

private fun startCamera() {
    val processCameraProvider = ProcessCameraProvider.getInstance(requireContext())
    val aspectRatioStrategy = AspectRatioStrategy.RATIO_4_3_FALLBACK_AUTO_STRATEGY

    processCameraProvider.addListener({
        try {
            cameraProvider = processCameraProvider.get()
            
            if (!cameraProvider!!.hasCamera(cameraSelector)) {
                Log.e("CameraError", "Selected camera does not exist")
                return@addListener
            }

            imageCapture = ImageCapture.Builder()
                .setCaptureMode(CAPTURE_MODE_MINIMIZE_LATENCY)
                .setResolutionSelector(ResolutionSelector.Builder()
                    .setResolutionStrategy(ResolutionStrategy(Size(800,600),
                        ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER))
                    .setAspectRatioStrategy(aspectRatioStrategy).build())
                .build()
            
            val previewUseCase = Preview.Builder().build()
            previewUseCase.setSurfaceProvider(binding.cameraView.surfaceProvider)

            cameraProvider?.unbindAll()
            // 同时绑定Preview和ImageCapture到生命周期
            cameraProvider?.bindToLifecycle(this, cameraSelector, previewUseCase, imageCapture)
        } catch (e: Exception) {
            Log.e("CameraError", "Start camera failed: ${e.message}", e)
        }
    }, ContextCompat.getMainExecutor(requireContext()))

    // 省略原有预览逻辑代码...
}

修改captureImage,无需再次绑定ImageCapture:

private fun captureImage() {
    val imageCapture = imageCapture ?: return

    imageCapture.takePicture(ContextCompat.getMainExecutor(requireContext()),
        object : ImageCapture.OnImageCapturedCallback() {
            @ExperimentalGetImage
            override fun onCaptureSuccess(image: ImageProxy) {
                super.onCaptureSuccess(image)
                val capturedImageBitmap = image.image?.toBitmap()?.rotate(image.imageInfo.rotationDegrees.toFloat())

                cameraProvider?.unbindAll()
                binding.imagePreview.isVisible = true
                binding.imagePreview.setImageBitmap(capturedImageBitmap?.let { flip(it) })
                capturedImageBitmap?.let { validateUserImage(it) }
                image.close()
            }

            override fun onError(exception: ImageCaptureException) {
                super.onError(exception)
                Log.e("CameraError", "Capture failed: ${exception.message}", exception)
            }
        })
}

3. 分辨率配置过于严苛

固定设置800x600分辨率,部分设备的摄像头不支持该尺寸,导致相机无法匹配UseCase。

修复方案:
放宽分辨率策略,让CameraX自动适配设备支持的分辨率:

imageCapture = ImageCapture.Builder()
    .setCaptureMode(CAPTURE_MODE_MINIMIZE_LATENCY)
    .setResolutionSelector(ResolutionSelector.Builder()
        .setAspectRatioStrategy(aspectRatioStrategy)
        .build())
    .build()

或保留 fallback 逻辑但不强制固定尺寸:

.setResolutionStrategy(ResolutionStrategy(
    Size(1280, 960),
    ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER
))

4. 异常捕获范围不足

原代码仅捕获IOException,但bindToLifecycle可能抛出IllegalArgumentException等其他异常,导致崩溃未被处理。

修复方案:
扩大异常捕获范围,记录完整异常栈:

try {
    // 相机初始化逻辑代码
} catch (e: Exception) {
    Log.e("CameraError", "Camera initialization failed", e)
    // 可添加用户提示,如Toast显示相机启动失败
}

内容的提问来源于stack exchange,提问作者Sunny Gahlot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 18:35:07