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

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()
    }
}

关键修改说明

  1. 改用InputImage.fromMediaImage:直接使用相机提供的MediaImage对象,MLKit会自动处理图像格式、旋转等细节,避免手动处理YUV数据的错误。
  2. 修正条码类型判断:将检测目标改为Barcode.TYPE_QR_CODE,符合QR码的标准类型定义。
  3. 完善资源释放逻辑:用try-catch包裹整个分析流程,确保任何异常情况下都能关闭ImageProxy,防止相机资源泄漏。
  4. 优化调试逻辑:仅在检测到QR码时弹出提示,其他情况用Logcat输出信息,避免弹窗干扰扫描流程。
  5. 补充权限拒绝提示:添加权限被拒绝后的用户引导,提升应用体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 16:55:00