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

使用Jetpack Compose的Firebase条码扫描器无法扫描且分析器仅运行一次

问题原因

  • ImageAnalysis的imageProxy没有在所有执行路径下关闭:只有扫码成功、扫码失败两个分支关闭了imageProxy,当imageProxy.image为空、或者两次分析间隔不足1秒直接跳过逻辑时,imageProxy未被释放,导致相机后续帧无法回调,出现分析器仅执行一次的问题。
  • 相机初始化、用例绑定、线程池初始化逻辑全部放在AndroidView的update回调中,Compose重组时update会多次触发,导致相机反复重新绑定,分析逻辑异常,同时线程池反复创建不会释放会造成内存泄漏。
  • BarcodeScanner每次分析时重复创建实例,存在不必要的性能损耗。

修复方案

1. 修复CameraPreview逻辑

把相机相关的初始化逻辑移出update回调,用LaunchedEffect保证仅初始化一次,线程池记得在页面销毁时关闭:

@Composable
fun CameraPreview(
    data: CameraPreviewData,
) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    // 线程池全局唯一,页面销毁时关闭
    val cameraExecutor = remember { Executors.newSingleThreadExecutor() }
    DisposableEffect(Unit) {
        onDispose {
            cameraExecutor.shutdown()
        }
    }

    AndroidView(
        modifier = Modifier.fillMaxSize(),
        factory = { androidViewContext ->
            PreviewView(androidViewContext).apply {
                scaleType = PreviewView.ScaleType.FILL_CENTER
                layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                )
                implementationMode = PreviewView.ImplementationMode.COMPATIBLE
            }
        },
        update = { previewView ->
            // 用LaunchedEffect保证相机仅初始化绑定一次
            LaunchedEffect(previewView) {
                val cameraSelector = CameraSelector.Builder()
                    .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                    .build()
                val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
                val cameraProvider = cameraProviderFuture.get()

                val preview = Preview.Builder()
                    .build()
                    .also {
                        it.setSurfaceProvider(previewView.surfaceProvider)
                    }
                val barcodeAnalyser = BarcodeAnalyser { barcodes ->
                    barcodes.forEach { barcode ->
                        barcode.rawValue?.let { barcodeValue ->
                            logError("Barcode value detected: ${barcodeValue}.")
                            // 其他处理逻辑
                        }
                    }
                }
                val imageAnalysis = ImageAnalysis.Builder()
                    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                    .build()
                    .also {
                        it.setAnalyzer(cameraExecutor, barcodeAnalyser)
                    }

                try {
                    cameraProvider.unbindAll()
                    cameraProvider.bindToLifecycle(
                        lifecycleOwner,
                        cameraSelector,
                        preview,
                        imageAnalysis
                    )
                } catch (exception: Exception) {
                    logError("Use case binding failed with exception : $exception")
                }
            }
        }
    )
}

2. 修复BarcodeAnalyser逻辑

补全所有路径的imageProxy关闭逻辑,把BarcodeScanner改成成员变量:

class BarcodeAnalyser(
    private val onBarcodesDetected: (barcodes: List<Barcode>) -> Unit,
) : ImageAnalysis.Analyzer {
    private var lastAnalyzedTimestamp = 0L
    // 扫描器全局唯一,不要重复创建
    private val barcodeScanner by lazy {
        val options = BarcodeScannerOptions.Builder()
            .setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS)
            .build()
        BarcodeScanning.getClient(options)
    }

    override fun analyze(imageProxy: ImageProxy) {
        logError("Inside analyze")
        val currentTimestamp = System.currentTimeMillis()
        // 间隔不够直接关闭imageProxy返回
        if (currentTimestamp - lastAnalyzedTimestamp < TimeUnit.SECONDS.toMillis(1)) {
            imageProxy.close()
            return
        }

        val imageToAnalyze = imageProxy.image ?: run {
            // image为空关闭imageProxy返回
            imageProxy.close()
            return
        }

        val imageToProcess = InputImage.fromMediaImage(imageToAnalyze, imageProxy.imageInfo.rotationDegrees)
        barcodeScanner.process(imageToProcess)
            .addOnSuccessListener { barcodes ->
                if (barcodes.isNotEmpty()) {
                    logError("Scanned: $barcodes")
                    onBarcodesDetected(barcodes)
                } else {
                    logError("No barcode scanned")
                }
            }
            .addOnFailureListener { exception ->
                logError("BarcodeAnalyser: Something went wrong with exception: $exception")
            }
            // 无论成功失败都要关闭imageProxy
            .addOnCompleteListener {
                imageProxy.close()
            }
        lastAnalyzedTimestamp = currentTimestamp
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 20:45:02