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

如何基于CameraX实现条码连续扫描及AndroidX迁移建议咨询

Migrating to CameraX for Continuous Barcode Scanning (AndroidX Compatible)

Hey there! I totally get where you're coming from—moving to AndroidX is key for long-term app support, and ditching that old non-compatible zxing wrapper makes perfect sense. The good news is CameraX is fully capable of handling continuous barcode scanning, and it plays seamlessly with AndroidX. Here's a step-by-step breakdown to make your migration smooth:

1. Ditch the Old Library, Keep ZXing Core

First, remove the outdated me.dm7.barcodescanner:zxing:1.9.13 dependency from your module-level build.gradle. Instead, add the official ZXing core library—this is the actual barcode parsing logic, and it’s fully AndroidX-compliant:

// ZXing core for barcode decoding
implementation 'com.google.zxing:core:3.5.0'

2. Add CameraX Dependencies

Next, include the necessary CameraX libraries, all maintained by Google and optimized for AndroidX:

// CameraX core functionality
implementation 'androidx.camera:camera-core:1.3.0'
// Camera2 integration for broad device compatibility
implementation 'androidx.camera:camera-camera2:1.3.0'
// Lifecycle binding (auto-manages camera lifecycle)
implementation 'androidx.camera:camera-lifecycle:1.3.0'
// PreviewView for displaying camera feed
implementation 'androidx.camera:camera-view:1.3.0'

3. Implement Continuous Scanning with CameraX

CameraX’s ImageAnalysis use case is made for real-time frame processing—exactly what we need for continuous scanning. Here’s how to set it up:

Step 3.1: Add PreviewView to Your Layout

Replace the old scanner view with CameraX’s PreviewView in your XML layout:

<androidx.camera.view.PreviewView
    android:id="@+id/previewView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Step 3.2: Set Up CameraX Use Cases in Your Activity/Fragment

Bind the Preview (for camera feed display) and ImageAnalysis (for barcode scanning) use cases to your lifecycle. The ImageAnalysis.Analyzer will handle frame-by-frame processing:

class BarcodeScannerActivity : AppCompatActivity() {
    private lateinit var previewView: PreviewView
    private val barcodeReader = MultiFormatReader().apply {
        // Configure which barcode formats to scan (e.g., QR_CODE, CODE_128)
        val hints = mapOf(
            DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE)
        )
        setHints(hints)
    }
    // Track last scanned barcode to avoid duplicate results
    private var lastScannedBarcode: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_barcode_scanner)
        previewView = findViewById(R.id.previewView)

        // Request camera permission first (don't forget to handle denial!)
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            == PackageManager.PERMISSION_GRANTED
        ) {
            startCamera()
        } else {
            ActivityCompat.requestPermissions(
                this, arrayOf(Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST
            )
        }
    }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()

            // Preview use case: display camera feed
            val preview = Preview.Builder()
                .build()
                .also {
                    it.setSurfaceProvider(previewView.surfaceProvider)
                }

            // ImageAnalysis use case: process frames for barcodes
            val imageAnalysis = ImageAnalysis.Builder()
                // Use a balanced resolution for speed and accuracy
                .setTargetResolution(Size(1280, 720))
                // Keep only the latest frame to avoid backlog
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()
                .also {
                    it.setAnalyzer(Executors.newSingleThreadExecutor(), ImageAnalysis.Analyzer { imageProxy ->
                        scanBarcode(imageProxy)
                    })
                }

            // Bind use cases to the back camera
            try {
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(
                    this, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageAnalysis
                )
            } catch(exc: Exception) {
                Log.e("CameraX", "Use case binding failed", exc)
            }
        }, ContextCompat.getMainExecutor(this))
    }

    private fun scanBarcode(imageProxy: ImageProxy) {
        val mediaImage = imageProxy.image ?: run {
            imageProxy.close()
            return
        }
        // Convert camera frame to Bitmap for ZXing processing
        val bitmap = mediaImage.toBitmap()
        val source = BitmapLuminanceSource(bitmap)
        val result = try {
            barcodeReader.decode(BinaryBitmap(HybridBinarizer(source)))
        } catch (e: NotFoundException) {
            // No barcode found in this frame
            imageProxy.close()
            return
        } finally {
            barcodeReader.reset()
        }

        // Handle scanned result (avoid duplicates)
        val barcodeValue = result.text
        if (barcodeValue != lastScannedBarcode) {
            lastScannedBarcode = barcodeValue
            // Update UI on main thread
            runOnUiThread {
                Toast.makeText(this, "Scanned: $barcodeValue", Toast.LENGTH_SHORT).show()
                // Add your custom logic here (e.g., navigate, save data)
            }
        }

        imageProxy.close() // Always close to release camera resources
    }

    // Helper extension to convert CameraX Image to Bitmap
    private fun Image.toBitmap(): Bitmap {
        val yBuffer = planes[0].buffer
        val uBuffer = planes[1].buffer
        val vBuffer = planes[2].buffer

        val ySize = yBuffer.remaining()
        val uSize = uBuffer.remaining()
        val vSize = vBuffer.remaining()

        val nv21 = ByteArray(ySize + uSize + vSize)
        yBuffer.get(nv21, 0, ySize)
        vBuffer.get(nv21, ySize, vSize)
        uBuffer.get(nv21, ySize + vSize, uSize)

        val yuvImage = YuvImage(nv21, ImageFormat.NV21, width, height, null)
        val outputStream = ByteArrayOutputStream()
        yuvImage.compressToJpeg(Rect(0, 0, width, height), 100, outputStream)
        val imageBytes = outputStream.toByteArray()
        return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
    }

    companion object {
        private const val CAMERA_PERMISSION_REQUEST = 1001
    }
}

4. Key Tips for Smooth Scanning

  • Avoid Duplicates: Track the last scanned barcode like in the example to prevent spamming your UI with the same result every frame.
  • Boost Performance: For faster processing, skip Bitmap conversion and work directly with the YUV data from ImageProxy (it requires more code but uses less memory).
  • Filter Barcode Formats: Configure the MultiFormatReader to only scan the formats you need (e.g., only QR codes) to speed up decoding.
  • Lifecycle Safety: CameraX automatically handles opening/closing the camera when your activity/fragment starts/stops—no manual cleanup needed like with the old library.

Final Notes

This approach gives you full control over the scanning process, is fully AndroidX-compatible, and leverages Google’s CameraX library for consistent behavior across devices. The ZXing core library is actively maintained, so you’ll get updates for new barcode formats and bug fixes.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:03:40