Error : "Camera: Failed to set already detached use case active"

Error : "Camera: Failed to set already detached use case active"

正在尝试将 CameraX 与 Firebase Barcode Scanner 结合使用来扫描条码。

出现以下错误:

Camera: Failed to set already detached use case active

库版本:

// 相机 X
cameraXVersion = "1.0.0-beta02"

// 相机视图版本
cameraViewVersion = "1.0.0-alpha08"

// Firebase ML 愿景
firebaseMlVisionVersion = "24.0.1"

// Firebase ML 视觉条码模型
firebaseMlVisionBarcodeModelVersion = "16.0.2"

条码扫描器:

class BarcodeScanner(
    private val previewView: PreviewView,
    private val cameraExecutor: ExecutorService,
    private val context: Context,
    private val lifecycleOwner: LifecycleOwner,
    private val qrCodeAnalyser: QrCodeAnalyser
) {

    private var screenAspectRatio: Int
    private var rotation: Int
    private lateinit var cameraProvider: ProcessCameraProvider
    private var cameraSelector: CameraSelector
    private lateinit var preview: Preview
    private lateinit var imageAnalysis: ImageAnalysis
    private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>

    init {

        screenAspectRatio = getAspectRatio(previewView)
        if (LOGGING_ENABLED) {
            Log.e(TAG, "Preview aspect ratio: $screenAspectRatio")
        }

        rotation = previewView.display.rotation

        // Bind the camera provider to the life cycle owner
        cameraSelector = CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build()
    }


    // Declare and bind preview and analysis use cases
    fun bindCameraUseCases() {

        cameraProviderFuture = ProcessCameraProvider.getInstance(context)
        cameraProviderFuture.addListener(Runnable {

            // Camera provider
            cameraProvider = cameraProviderFuture.get()

            // Preview
            preview = Preview.Builder()
                // We request aspect ratio but no resolution
                .setTargetAspectRatio(screenAspectRatio)
                // Set initial target rotation
                .setTargetRotation(rotation)
                .build()

            // Attach the viewfinder's surface provider to preview use case
            preview.setSurfaceProvider(previewView.previewSurfaceProvider)

            // Image analysis
            imageAnalysis = ImageAnalysis.Builder()
                // We request aspect ratio but no resolution
                .setTargetAspectRatio(screenAspectRatio)
                // Set initial target rotation, we will have to call this again if rotation changes
                // during the lifecycle of this use case
                .setTargetRotation(rotation)
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()
                // The analyzer can then be assigned to the instance
                .also { it.setAnalyzer(cameraExecutor, qrCodeAnalyser) }

            // Must unbind the use-cases before rebinding them
            unbindCameraUseCases()

            try {
                // A variable number of use-cases can be passed here -
                // camera provides access to CameraControl & CameraInfo
                cameraProvider.bindToLifecycle(
                    lifecycleOwner, cameraSelector, preview, imageAnalysis
                )
            } catch (exc: Exception) {

                if (LOGGING_ENABLED) {
                    Log.e(TAG, "Use case binding failed", exc)
                }
            }
        }, ContextCompat.getMainExecutor(context))
    }

    fun unbindCameraUseCases() {

        cameraProvider.unbindAll()
    }

    //  Detecting the most suitable ratio for dimensions provided in @params by counting absolute
    //  of preview ratio to one of the provided values.
    private fun getAspectRatio(previewView: PreviewView): Int {

        // Get screen metrics used to setup camera for full screen resolution
        val metrics = DisplayMetrics().also { previewView.display.getRealMetrics(it) }
        if (LOGGING_ENABLED) {
            Log.e(TAG, "Screen metrics: ${metrics.widthPixels} x ${metrics.heightPixels}")
        }

        val width = metrics.widthPixels
        val height = metrics.heightPixels

        val previewRatio = max(width, height).toDouble() / min(width, height)
        if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
            return AspectRatio.RATIO_4_3
        }
        return AspectRatio.RATIO_16_9
    }


    companion object {

        private const val RATIO_4_3_VALUE = 4.0 / 3.0
        private const val RATIO_16_9_VALUE = 16.0 / 9.0
        private const val TAG = "Barcode Scanner"
        private val LOGGING_ENABLED = BuildConfig.DEBUG && loggingEnabled && barcodeLoggingEnabled
    }
}

布局XML:

<androidx.camera.view.PreviewView
    android:id="@+id/view_finder"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

由约束布局包裹。

片段代码:

private lateinit var barcodeScanner: BarcodeScanner
private val cameraExecutor = Executors.newSingleThreadExecutor()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    startBarcodeScanner()
}


private fun startBarcodeScanner() {

    val qrCodeAnalyser = QrCodeAnalyser { qrCodes ->

        qrCodes.forEach { qrCode ->

            qrCode.rawValue?.let { qrCodeValue ->

                Log.e(TAG, "QR Code detected: ${qrCode.rawValue}.")
                // Use case is to detect only the first QR code and then process it and navigate to next fragment

                if (::barcodeScanner.isInitialized) {
                    barcodeScanner.unbindCameraUseCases()
                }
                // Shut down our background executor
                cameraExecutor.shutdown()
            }
        }
    }

    barcodeScanner = BarcodeScanner(
        view_finder,
        cameraExecutor,
        requireContext(),
        viewLifecycleOwner,
        qrCodeAnalyser
    )

    view_finder.post {
        barcodeScanner.bindCameraUseCases()
    }
}

override fun onPause() { 
    super.onPause()
    if (::barcodeScanner.isInitialized) {
        barcodeScanner.unbindCameraUseCases()
    }
}

override fun onResume() {
    super.onResume()
    view_finder.post {
        if (::barcodeScanner.isInitialized) {
            barcodeScanner.bindCameraUseCases()
        }
    }
}

override fun onDestroyView() {
    super.onDestroyView()

    if (::barcodeScanner.isInitialized) {
        barcodeScanner.unbindCameraUseCases()
    }
    // Shut down our background executor
    cameraExecutor.shutdown()
}

所有不相关的代码都被剥离。

收到错误信息时的场景:

当我第二次及以后导航到该片段时。第一次没有报错。

感谢任何帮助。

附上完整的堆栈跟踪:

D/Camera: Use cases [Preview:androidx.camera.core.Preview-2b628f3b-651e-4185-8816-bacd182d7739, ImageAnalysis:androidx.camera.core.ImageAnalysis-16478fc0-c1ff-4cac-9fa0-46e218202cee] now ONLINE for camera 0 
D/UseCaseAttachState: Active and online use case: [] for camera: 0
D/Camera: Resetting Capture Session  
D/Camera: releasing session in state INITIALIZED  
D/Camera: Transitioning camera internal state: INITIALIZED --    OPENING  
D/Camera: Opening camera: 0  
D/UseCaseAttachState: All use case: [androidx.camera.core.ImageAnalysis-16478fc0-c1ff-4cac-9fa0-46e218202cee, androidx.camera.core.Preview-2b628f3b-651e-4185-8816-bacd182d7739] for camera: 0  
I/CameraManager: Using legacy camera HAL.  
I/art: Background partial concurrent mark sweep GC freed 26391(1116KB) AllocSpace objects, 27(17MB) LOS objects, 40% free, 20MB/33MB, paused 7.112ms total 172.514ms  
D/Camera: Use case ImageAnalysis:androidx.camera.core.ImageAnalysis-16478fc0-c1ff-4cac-9fa0-46e218202cee ACTIVE for camera 0  
E/Camera: Failed to set already detached use case active  
D/Camera: Use case Preview:androidx.camera.core.Preview-2b628f3b-651e-4185-8816-bacd182d7739 ACTIVE for camera 0  
E/Camera: Failed to set already detached use case active  
D/Camera: Use cases [ImageAnalysis:androidx.camera.core.ImageAnalysis-16478fc0-c1ff-4cac-9fa0-46e218202cee] now OFFLINE for camera 0  
D/UseCaseAttachState: Active and online use case: [] for camera: 0  
D/Camera: Resetting Capture Session  
D/Camera: releasing session in state OPENING  
D/Camera: Use case Preview:androidx.camera.core.Preview-2b628f3b-651e-4185-8816-bacd182d7739 INACTIVE for camera 0  
D/UseCaseAttachState: Active and online use case: [] for camera: 0  
D/Camera: Use cases [Preview:androidx.camera.core.Preview-2b628f3b-651e-4185-8816-bacd182d7739] now OFFLINE for camera 0  
D/Camera: Resetting Capture Session  
D/Camera: releasing session in state OPENING  
D/Camera: Closing camera: 0  
D/Camera: Transitioning camera internal state: OPENING --    CLOSING  
D/UseCaseAttachState: Active and online use case: [] for camera: 0  
D/UseCaseAttachState: Active and online use case: [] for camera: 0  
D/Camera: Use cases [ImageAnalysis:androidx.camera.core.ImageAnalysis-e1f008e1-b782-4a19-940a-e75166d230a5, Preview:androidx.camera.core.Preview-4021921f-00eb-46dd-bce5-fb77d209bd34] now ONLINE for camera 0  
D/UseCaseAttachState: Active and online use case: [] for camera: 0  
D/Camera: Resetting Capture Session  
D/Camera: releasing session in state CLOSING  
D/Camera: Transitioning camera internal state: CLOSING --    REOPENING  
D/Camera: Use case ImageAnalysis:androidx.camera.core.ImageAnalysis-e1f008e1-b782-4a19-940a-e75166d230a5 ACTIVE for camera 0  
D/UseCaseAttachState: Active and online use case: [androidx.camera.core.ImageAnalysis-e1f008e1-b782-4a19-940a-e75166d230a5] for camera: 0  
D/Camera: Use case Preview:androidx.camera.core.Preview-4021921f-00eb-46dd-bce5-fb77d209bd34 ACTIVE for camera 0  
D/UseCaseAttachState: Active and online use case: [androidx.camera.core.Preview-4021921f-00eb-46dd-bce5-fb77d209bd34, androidx.camera.core.ImageAnalysis-e1f008e1-b782-4a19-940a-e75166d230a5] for camera: 0  
D/Camera: CameraDevice.onOpened(): 0  
D/Camera: Transitioning camera internal state: REOPENING --    OPENED  
D/UseCaseAttachState: All use case: [androidx.camera.core.Preview-4021921f-00eb-46dd-bce5-fb77d209bd34, androidx.camera.core.ImageAnalysis-e1f008e1-b782-4a19-940a-e75166d230a5] for camera: 0  
D/CaptureSession: Opening capture session.  
I/CameraDeviceState: Legacy camera service transitioning to state CONFIGURING  
I/RequestThread-0: Configure outputs: 2 surfaces configured.  
D/Camera: app passed NULL surface  
I/CameraDeviceState: Legacy camera service transitioning to state IDLE  
D/CaptureSession: Attempting to send capture request onConfigured  
D/CaptureSession: Issuing request for session.  
I/RequestQueue: Repeating capture request set.  
D/CaptureSession: CameraCaptureSession.onConfigured() mState=OPENED  
D/CaptureSession: CameraCaptureSession.onReady() OPENED  
W/LegacyRequestMapper: convertRequestMetadata - control.awbRegions setting is not supported, ignoring value  
W/LegacyRequestMapper: Only received metering rectangles with weight 0.  
W/LegacyRequestMapper: Only received metering rectangles with weight 0.  
I/art: Background partial concurrent mark sweep GC freed 12590(707KB) AllocSpace objects, 1(608KB) LOS objects, 40% free, 20MB/34MB, paused 2.229ms total 165.276ms  
I/CameraDeviceState: Legacy camera service transitioning to state CAPTURING  

我在 Google 问题跟踪器中创建了一个问题:
https://issuetracker.google.com/issues/154422490?enable_mat=true

响应:

Thanks for filing this issue, this issue can be safely ignored - we have a todo for removing unnecessarily logging. This should be fixed shortly.