使用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
相关产品推荐
相关产品推荐

