如何基于CameraX实现条码连续扫描及AndroidX迁移建议咨询
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
MultiFormatReaderto 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

