基于CameraX与ML Kit:如何连接BarcodeAnalyzer与QrActivity实现二维码识别
解决CameraX + ML Kit二维码扫描的类连接与实例化问题
看起来你现在的核心问题是没有把BarcodeAnalyzer正确绑定到CameraX的ImageAnalysis用例,同时Analyzer内部也有几个关键问题(比如未初始化Context、未处理识别结果、未关闭ImageProxy)导致功能失效。我来一步步帮你解决:
第一步:修复BarcodeAnalyzer类
我们需要给Analyzer添加构造函数传入Context,定义回调接口把扫描结果传给Activity,同时修复ImageProxy的关闭逻辑:
package ge.softservice.nfcwithactivties import android.annotation.SuppressLint import android.content.Context import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import com.google.firebase.ml.vision.FirebaseVision import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetectorOptions import com.google.firebase.ml.vision.common.FirebaseVisionImage import com.google.firebase.ml.vision.common.FirebaseVisionImageMetadata // 定义回调接口,把扫描结果传递给Activity处理 interface BarcodeScanListener { fun onBarcodeScanned(barcode: FirebaseVisionBarcode) fun onScanFailed(error: Exception) } class BarcodeAnalyzer( private val context: Context, private val scanListener: BarcodeScanListener ) : ImageAnalysis.Analyzer { private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) { 0 -> FirebaseVisionImageMetadata.ROTATION_0 90 -> FirebaseVisionImageMetadata.ROTATION_90 180 -> FirebaseVisionImageMetadata.ROTATION_180 270 -> FirebaseVisionImageMetadata.ROTATION_270 else -> throw Exception("Rotation must be 0, 90, 180, or 270.") } @SuppressLint("UnsafeExperimentalUsageError") override fun analyze(imageProxy: ImageProxy) { val mediaImage = imageProxy.image ?: run { imageProxy.close() // 无有效图像直接关闭,避免阻塞CameraX return } val imageRotation = degreesToFirebaseRotation(imageProxy.imageInfo.rotationDegrees) val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation) val options = FirebaseVisionBarcodeDetectorOptions.Builder() .setBarcodeFormats(FirebaseVisionBarcode.FORMAT_QR_CODE) .build() val detector = FirebaseVision.getInstance().getVisionBarcodeDetector(options) detector.detectInImage(image) .addOnSuccessListener { barcodes -> if (barcodes.isNotEmpty()) { // 将扫描结果通过回调传给Activity scanListener.onBarcodeScanned(barcodes[0]) } imageProxy.close() // 必须关闭ImageProxy,否则CameraX会停止输出帧 } .addOnFailureListener { exception -> scanListener.onScanFailed(exception) imageProxy.close() // 失败场景也要关闭 } } }
关键修复点:
- 通过构造函数传入
Context和BarcodeScanListener,解决未初始化问题 - 必须在异步回调末尾调用
imageProxy.close(),避免CameraX阻塞 - 用回调接口实现Analyzer与Activity的解耦,让Activity处理UI逻辑
- 增加了图像为空的边界处理
第二步:修改QrActivity,绑定ImageAnalysis用例
现在需要在startCamera()方法中创建ImageAnalysis实例,绑定我们的BarcodeAnalyzer,并且实现回调接口处理扫描结果:
package ge.softservice.nfcwithactivties import android.Manifest import android.content.pm.PackageManager import android.os.Bundle import android.util.Log import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.* import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import kotlinx.android.synthetic.main.activity_qr.* import java.util.concurrent.ExecutorService import java.util.concurrent.Executors class QrActivity : AppCompatActivity(), BarcodeScanListener { private var preview: Preview? = null private var imageAnalyzer: ImageAnalysis? = null private var camera: Camera? = null private lateinit var cameraExecutor: ExecutorService override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_CODE_PERMISSIONS) { if (allPermissionsGranted()) { startCamera() } else { Toast.makeText( this, "Permissions not granted by the user.", Toast.LENGTH_SHORT ).show() finish() } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_qr) if (allPermissionsGranted()) { startCamera() } else { ActivityCompat.requestPermissions( this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS ) } cameraExecutor = Executors.newSingleThreadExecutor() } private fun startCamera() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() // 初始化Preview用例 preview = Preview.Builder() .build() .also { it.setSurfaceProvider(viewFinder.createSurfaceProvider()) } // 初始化ImageAnalysis用例并绑定BarcodeAnalyzer imageAnalyzer = ImageAnalysis.Builder() // 选择只保留最新帧的策略,适合实时扫描 .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() .also { // 实例化Analyzer,传入Context和当前Activity作为回调接收者 it.setAnalyzer(cameraExecutor, BarcodeAnalyzer(this, this)) } // 选择后置摄像头 val cameraSelector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK) .build() try { cameraProvider.unbindAll() // 绑定Preview和ImageAnalysis两个用例到Camera camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageAnalyzer ) } catch (exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(this)) } // 实现回调方法,处理扫描成功的结果 override fun onBarcodeScanned(barcode: FirebaseVisionBarcode) { val barcodeContent = barcode.rawValue ?: "二维码无内容" // 切换到主线程显示Toast,避免后台线程操作UI报错 runOnUiThread { Toast.makeText(this, "扫描到:$barcodeContent", Toast.LENGTH_LONG).show() // 可选:扫描成功后关闭页面或执行其他逻辑 // finish() } } // 实现回调方法,处理扫描失败的情况 override fun onScanFailed(error: Exception) { runOnUiThread { Toast.makeText(this, "扫描失败:${error.message}", Toast.LENGTH_SHORT).show() } Log.e(TAG, "Scan failed", error) } private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED } companion object { private const val TAG = "QrScanActivity" private const val REQUEST_CODE_PERMISSIONS = 10 private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA) } }
关键修改点:
- 让
QrActivity实现BarcodeScanListener接口,接收扫描结果 - 在
startCamera()中创建并绑定了ImageAnalysis用例,这是你之前缺失的核心步骤 - 使用
cameraExecutor后台线程处理图像分析,符合CameraX的线程要求 - 在回调中用
runOnUiThread()确保UI操作在主线程执行
为什么之前的代码不工作?
- 未绑定ImageAnalysis用例:你原来的
startCamera()只绑定了Preview,BarcodeAnalyzer根本没有被CameraX调用 - Context未初始化:原Analyzer的
lateinit var context没有赋值,实际执行时会触发空指针(只是因为Analyzer没被调用所以没报错) - 未关闭ImageProxy:CameraX要求每次分析后必须关闭ImageProxy,否则会停止输出新帧,导致扫描卡住
- 线程问题:ML Kit的回调在后台线程,直接调用Toast会触发UI线程异常
现在按照上面的代码修改后,就能正常实现二维码扫描功能了。
内容的提问来源于stack exchange,提问作者X Y
相关产品推荐
相关产品推荐

