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

应用启动首次扫码触发重复条目与Intent问题排查求助

问题修复方案

问题总结

部分设备上应用启动后首次扫码会出现重复识别(生成两条数据库记录、触发两次跳转),后续扫码恢复正常但重启后复现,同时扫码过程存在卡顿,仅特定设备表现不稳定。


核心问题定位

  1. 异步扫描未及时锁定处理状态,导致连续帧重复触发识别
  2. 每次图像分析都创建新的扫描客户端,额外增加性能开销引发卡顿
  3. 图像分析在主线程执行,阻塞UI导致扫码卡顿

具体修复步骤

1. 复用BarcodeScanning客户端,减少资源开销

每次processImageProxy创建新的扫描客户端会浪费资源,改为在绑定相机用例时创建并复用:

class HomeFragment : Fragment() {
    private var barcodeScanner: BarcodeScanner? = null
    // ... 原有成员变量

    private fun bindCameraUseCases(cameraProvider: ProcessCameraProvider) {
        // ... 原有布局和扫描配置逻辑
        val scannerOptions = if (binding.toggleScanner.isChecked) {
            // 条码格式配置
            BarcodeScannerOptions.Builder()
                .setBarcodeFormats(
                    Barcode.FORMAT_CODE_128,
                    Barcode.FORMAT_CODE_39,
                    // ... 其他条码格式
                )
                .build()
        } else {
            // QR码格式配置
            BarcodeScannerOptions.Builder()
                .setBarcodeFormats(Barcode.FORMAT_QR_CODE)
                .build()
        }
        // 创建并保存扫描客户端
        barcodeScanner = BarcodeScanning.getClient(scannerOptions)

        val imageAnalysis = ImageAnalysis.Builder()
            .setTargetRotation(binding.previewView.display?.rotation ?: Display.DEFAULT_DISPLAY)
            .build()
        
        // 用后台线程执行分析,避免卡顿
        imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor()) { imageProxy ->
            processImageProxy(imageProxy)
        }
        // ... 绑定相机生命周期
    }

    @OptIn(ExperimentalGetImage::class)
    private fun processImageProxy(imageProxy: ImageProxy) {
        Log.d("HomeFragment", "processImageProxy called with isProcessingScan: $isProcessingScan")

        if (isProcessingScan) {
            imageProxy.close()
            return
        }

        val mediaImage = imageProxy.image ?: run {
            imageProxy.close()
            return
        }
        val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

        // 复用已创建的扫描客户端
        barcodeScanner?.process(image)
            ?.addOnSuccessListener { barcodes ->
                if (barcodes.isNotEmpty()) {
                    handleScannedResult(barcodes[0])
                } else {
                    isProcessingScan = false
                }
            }
            ?.addOnFailureListener {
                imageProxy.close()
                isProcessingScan = false
            }
            ?.addOnCompleteListener {
                imageProxy.close()
            }
    }
}

2. 提前锁定扫描状态,避免重复触发

在开始处理图像帧时立即锁定状态,防止连续帧重复识别:

@OptIn(ExperimentalGetImage::class)
private fun processImageProxy(imageProxy: ImageProxy) {
    Log.d("HomeFragment", "processImageProxy called with isProcessingScan: $isProcessingScan")

    if (isProcessingScan) {
        imageProxy.close()
        return
    }
    // 立即锁定状态,防止后续帧重复处理
    isProcessingScan = true

    val mediaImage = imageProxy.image ?: run {
        imageProxy.close()
        isProcessingScan = false // 释放状态
        return
    }
    val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

    barcodeScanner?.process(image)
        ?.addOnSuccessListener { barcodes ->
            if (barcodes.isNotEmpty()) {
                handleScannedResult(barcodes[0])
            } else {
                isProcessingScan = false
            }
        }
        ?.addOnFailureListener {
            imageProxy.close()
            isProcessingScan = false
        }
        ?.addOnCompleteListener {
            imageProxy.close()
        }
}

同时修改handleScannedResult,移除冗余的状态设置并优化重置逻辑:

private fun handleScannedResult(barcode: Barcode) {
    val result = barcode.rawValue
    val barcodeType = barcode.format
    if (result != null) {
        if (processedBarcodes.contains(result)) {
            isProcessingScan = false
            return
        }
        processedBarcodes.add(result)

        // 数据库插入逻辑
        val date = SimpleDateFormat("dd MMM yyyy", Locale.getDefault()).format(Date())
        val time = SimpleDateFormat("hh:mm a", Locale.getDefault()).format(Date())
        lifecycleScope.launch {
            val historyItem = HistoryItemEntity(
                content = result,
                date = date,
                time = time,
                resultType = "scanned",
                barcodeType = getBarcodeTypeName(barcodeType)
            )
            AppDatabase.getDatabase(requireContext()).historyDao().insertHistoryItem(historyItem)
        }

        // 跳转结果页
        val intent = Intent(requireContext(), ResultActivity::class.java).apply {
            putExtra("SCANNED_RESULT", result)
            putExtra("BARCODE_TYPE", getBarcodeTypeName(barcodeType))
        }
        startActivity(intent)

        // 缩短延迟时间,避免影响后续扫码
        Handler(Looper.getMainLooper()).postDelayed({
            isProcessingScan = false
            processedBarcodes.clear()
        }, 2000)
    } else {
        isProcessingScan = false
    }
}

3. 优化扫码类型切换逻辑

避免直接调用cameraProviderFuture.get()引发异常,改用监听回调:

private fun startQRScanner() {
    lifecycleScope.launch {
        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()
            isProcessingScan = false
            processedBarcodes.clear()
            bindCameraUseCases(cameraProvider)
        }, ContextCompat.getMainExecutor(requireContext()))
    }
}

private fun startBarcodeScanner() {
    lifecycleScope.launch {
        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()
            isProcessingScan = false
            processedBarcodes.clear()
            bindCameraUseCases(cameraProvider)
        }, ContextCompat.getMainExecutor(requireContext()))
    }
}

修复效果说明

  • 提前锁定扫描状态,从根源上阻止同一条码被连续识别
  • 复用扫描客户端,减少对象创建开销,缓解扫码卡顿
  • 后台线程执行图像分析,避免阻塞主线程导致的UI卡顿
  • 优化状态重置逻辑,确保扫码完成或切换类型后状态正确恢复

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 22:04:56