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

Jetpack Compose+ML Kit条码扫描如何限制仅识别触发一次

问题根因

重复触发导航的核心原因是ImageAnalysis会持续推送相机帧到分析器,只要条码停留在取景范围内,连续多帧识别到相同条码时就会重复执行回调。现有代码未做识别成功后的拦截或分析流程终止逻辑,最终导致重复打开页面。

可落地解决方案

两种方案搭配使用稳定性最高:

方案1:识别到有效条码后立刻终止分析流程

识别成功后第一时间阻断后续帧分析,从根源上避免重复识别:

  1. 在AndroidView的factory块中留存imageAnalysis和cameraProvider的实例引用
  2. 条码识别回调触发时,先清空分析器/解绑分析用例,再执行导航逻辑

修改后的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 12:25:18