Jetpack Compose+ML Kit条码扫描如何限制仅识别触发一次
问题根因
重复触发导航的核心原因是ImageAnalysis会持续推送相机帧到分析器,只要条码停留在取景范围内,连续多帧识别到相同条码时就会重复执行回调。现有代码未做识别成功后的拦截或分析流程终止逻辑,最终导致重复打开页面。
可落地解决方案
两种方案搭配使用稳定性最高:
方案1:识别到有效条码后立刻终止分析流程
识别成功后第一时间阻断后续帧分析,从根源上避免重复识别:
- 在
AndroidView的factory块中留存imageAnalysis和cameraProvider的实例引用 - 条码识别回调触发时,先清空分析器/解绑分析用例,再执行导航逻辑
修改后的AndroidView代码如下:
AndroidView( factory = { context -> val previewView = PreviewView(context) val preview = Preview.Builder().build() val selector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK) .build() preview.setSurfaceProvider(previewView.surfaceProvider) val imageAnalysis = ImageAnalysis.Builder() .setTargetResolution(Size(previewView.width, previewView.height)) .setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST) .build() val cameraProvider = cameraProviderFuture.get() imageAnalysis.setAnalyzer( ContextCompat.getMainExecutor(context), BarCodeAnalyzer { result -> code = result // 识别成功先阻断分析流程,二选一即可 imageAnalysis.clearAnalyzer() // 清空已设置的分析器,后续帧不再送入识别逻辑 // cameraProvider.unbind(imageAnalysis) // 直接解绑ImageAnalysis用例,效果一致 // 阻断完成后再执行导航 navController.navigate(Screen.FormProduct.route + "?barcode=$code") } ) try { cameraProvider.bindToLifecycle( lifecycleOwner, selector, preview, imageAnalysis ) } catch (e: Exception) { e.printStackTrace() } previewView }, modifier = Modifier.fillMaxSize() )
如果后续从结果页返回扫描页需要恢复扫描,重新给imageAnalysis设置Analyzer实例,或重新绑定imageAnalysis到生命周期即可。
方案2:在分析器层增加拦截逻辑(与方案1搭配使用)
原有BarCodeAnalyzer中的currentTimestamp字段未实际生效,可以改造为节流+重复码拦截逻辑,同时优化性能问题:
- 增加扫描成功标记位,避免同一批次帧重复触发回调
- 增加时间节流和条码内容校验,短时间内的相同条码结果直接忽略
- 复用BarcodeScanner实例,不要每帧都新建,减少性能开销
修改后的BarCodeAnalyzer代码如下:
class BarCodeAnalyzer( private val onBarCodeScanned: (String) -> Unit, ) : ImageAnalysis.Analyzer { private var isScanned = false // 识别节流间隔,单位ms,可根据业务调整 private val throttleInterval = 1500L private var lastScannedTime = 0L private var lastScannedCode = "" // 复用scanner实例,避免每帧新建 private val options = BarcodeScannerOptions.Builder() .setBarcodeFormats(Barcode.FORMAT_EAN_13, Barcode.FORMAT_EAN_8) .build() private val scanner = BarcodeScanning.getClient(options) @SuppressLint("UnsafeOptInUsageError") override fun analyze(imageProxy: ImageProxy) { val image = imageProxy.image if (image != null) { val inputImage = InputImage.fromMediaImage(image, imageProxy.imageInfo.rotationDegrees) scanner.process(inputImage) .addOnSuccessListener { barcodes -> if (barcodes.isNotEmpty() && !isScanned) { barcodes.firstOrNull()?.rawValue?.let { barcode -> val currentTime = System.currentTimeMillis() // 超过节流间隔、或条码与上次识别结果不一致才触发回调 if (currentTime - lastScannedTime > throttleInterval || barcode != lastScannedCode) { lastScannedTime = currentTime lastScannedCode = barcode isScanned = true Log.i(TAG, "Barcode : $barcode") onBarCodeScanned(barcode) } } } } .addOnCompleteListener { imageProxy.close() } } else { imageProxy.close() } } // 重置扫描状态,返回扫描页时调用即可恢复扫描能力 fun reset() { isScanned = false lastScannedTime = 0L lastScannedCode = "" } }
额外兜底优化
如果使用Navigation组件做页面导航,建议在导航前增加目的地判断,避免重复导航问题:
val currentDestId = navController.currentDestination?.id // 替换为你自己的FormProduct页对应的目的地ID if (currentDestId != R.id.screen_form_product) { navController.navigate(Screen.FormProduct.route + "?barcode=$code") }
内容的提问来源于stack exchange,提问作者Gon3s
相关产品推荐
相关产品推荐

