Android Studio中Kotlin QR码扫描功能异常排查求助
解决QR码扫描工具的检测异常问题
问题核心分析
从你提供的代码来看,检测模块异常主要由以下几个关键问题导致:
- 图像数据处理错误:手动提取NV21格式数据时仅取了Y分量,缺少UV分量,导致MLKit无法正确解析完整图像。
- 条码类型判断错误:仅检测
Barcode.TYPE_TEXT,但QR码的标准类型是Barcode.TYPE_QR_CODE,即使包含文本内容,类型也不会是TEXT。 - 调试逻辑干扰:每次分析都弹出AlertDialog会阻塞相机帧处理,甚至导致扫描流程卡顿。
- 资源释放不严谨:ImageProxy关闭时机依赖异步回调,若出现异常可能导致相机资源泄漏。
修复后的完整代码
package com.example.tekanalyzer import android.Manifest import android.app.Activity import android.content.Context import android.content.pm.PackageManager import android.os.Bundle import android.util.Log import android.util.Size import android.view.GestureDetector import android.view.MotionEvent import android.view.ScaleGestureDetector import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.Camera import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.view.doOnLayout import androidx.lifecycle.LifecycleOwner import com.google.mlkit.vision.barcode.Barcode import com.google.mlkit.vision.barcode.BarcodeScanner import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.common.InputImage import java.util.concurrent.ExecutorService import java.util.concurrent.Executors class QRCodeScannerActivity : AppCompatActivity(), LifecycleOwner { private lateinit var viewFinder: PreviewView private lateinit var cameraExecutor: ExecutorService private var camera: Camera? = null private lateinit var scaleGestureDetector: ScaleGestureDetector private lateinit var gestureDetector: GestureDetector companion object { const val REQUEST_CODE_PERMISSIONS = 10 val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA) private const val TAG = "QRCodeScanner" } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_qrcode_scanner) viewFinder = findViewById(R.id.viewFinder) cameraExecutor = Executors.newSingleThreadExecutor() scaleGestureDetector = ScaleGestureDetector(this, ScaleListener()) gestureDetector = GestureDetector(this, GestureListener()) viewFinder.doOnLayout { if (allPermissionsGranted()) { startCamera() } else { ActivityCompat.requestPermissions( this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS ) } } } private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { ContextCompat.checkSelfPermission( baseContext, it ) == PackageManager.PERMISSION_GRANTED } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_CODE_PERMISSIONS) { if (allPermissionsGranted()) { startCamera() } else { AlertDialog.Builder(this) .setMessage("相机权限是扫描功能的必要条件") .setPositiveButton("确定") { dialog, _ -> dialog.dismiss() } .show() } } } private fun startCamera() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() val preview = Preview.Builder().build() val imageAnalysis = ImageAnalysis.Builder() .setTargetResolution(Size(1280, 720)) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() val qrCodeAnalyzer = QRCodeImageAnalyzer(this) imageAnalysis.setAnalyzer(cameraExecutor, qrCodeAnalyzer) val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { cameraProvider.unbindAll() camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageAnalysis ) preview.setSurfaceProvider(viewFinder.surfaceProvider) } catch (exc: Exception) { Log.e(TAG, "相机绑定失败", exc) } }, ContextCompat.getMainExecutor(this)) } override fun onTouchEvent(event: MotionEvent): Boolean { scaleGestureDetector.onTouchEvent(event) gestureDetector.onTouchEvent(event) return super.onTouchEvent(event) } override fun onDestroy() { super.onDestroy() cameraExecutor.shutdown() } private inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() { override fun onScale(detector: ScaleGestureDetector): Boolean { val zoomRatio = camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 1f val scaleFactor = detector.scaleFactor val newZoomRatio = zoomRatio * scaleFactor camera?.cameraControl?.setZoomRatio(newZoomRatio) return true } } private inner class GestureListener : GestureDetector.SimpleOnGestureListener() { override fun onDoubleTap(e: MotionEvent): Boolean { // 可添加双击变焦逻辑,比如切换1x和2x return super.onDoubleTap(e) } } } class QRCodeImageAnalyzer(private val context: Context) : ImageAnalysis.Analyzer { private val scanner: BarcodeScanner = BarcodeScanning.getClient() private val TAG = "QRCodeAnalyzer" override fun analyze(image: ImageProxy) { try { val rotationDegrees = image.imageInfo.rotationDegrees val mediaImage = image.image ?: run { Log.e(TAG, "获取相机图像失败") image.close() return } val inputImage = InputImage.fromMediaImage(mediaImage, rotationDegrees) scanner.process(inputImage) .addOnSuccessListener { barcodes -> for (barcode in barcodes) { if (barcode.valueType == Barcode.TYPE_QR_CODE) { barcode.displayValue?.let { value -> Log.d(TAG, "检测到QR码: $value") (context as Activity).runOnUiThread { showQRCodeDetectedDialog(value) } } } } } .addOnFailureListener { e -> Log.e(TAG, "QR码分析失败", e) } .addOnCompleteListener { image.close() } } catch (e: Exception) { Log.e(TAG, "分析流程异常", e) image.close() } } private fun showQRCodeDetectedDialog(qrCodeValue: String) { if ((context as Activity).isFinishing) return val dialogBuilder = AlertDialog.Builder(context) dialogBuilder.setMessage("检测到QR码:\n$qrCodeValue") .setPositiveButton("确定") { dialog, _ -> dialog.dismiss() } val dialog = dialogBuilder.create() dialog.show() } }
关键修改说明
- 改用
InputImage.fromMediaImage:直接使用相机提供的MediaImage对象,MLKit会自动处理图像格式、旋转等细节,避免手动处理YUV数据的错误。 - 修正条码类型判断:将检测目标改为
Barcode.TYPE_QR_CODE,符合QR码的标准类型定义。 - 完善资源释放逻辑:用try-catch包裹整个分析流程,确保任何异常情况下都能关闭ImageProxy,防止相机资源泄漏。
- 优化调试逻辑:仅在检测到QR码时弹出提示,其他情况用Logcat输出信息,避免弹窗干扰扫描流程。
- 补充权限拒绝提示:添加权限被拒绝后的用户引导,提升应用体验。
内容的提问来源于stack exchange,提问作者user22259895
相关产品推荐
相关产品推荐

