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

Android 12平板上Kotlin+ZXing无法读取二维码问题求助

二维码读取兼容性问题排查与修复

问题背景

我是Kotlin新手,开发二维码读取应用时遇到设备兼容性问题:

  • 采用CameraX + Zxing实现二维码解析,核心代码如下
  • 项目最低兼容Android SDK 21,依赖配置见下方
  • 测试结果:Android模拟器、8.7英寸Android 14平板(三星SM-T225)可正常读取;10英寸Android 12平板(三星SM-T505)无法读取,移除try-catch后抛出com.google.zxing.NotFoundException

核心解析代码

import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import com.google.zxing.BinaryBitmap
import com.google.zxing.MultiFormatReader
import com.google.zxing.Reader
import com.google.zxing.common.HybridBinarizer

class QRCodeAnalyzer(private val qrCodeListener: (String) -> Unit) : ImageAnalysis.Analyzer {

    private val reader: Reader = MultiFormatReader()

    override fun analyze(imageProxy: ImageProxy) {
        val buffer = imageProxy.planes[0].buffer
        val data = ByteArray(buffer.capacity())
        buffer.get(data)
        val source = com.google.zxing.PlanarYUVLuminanceSource(
            data,
            imageProxy.width,
            imageProxy.height,
            0,
            0,
            imageProxy.width,
            imageProxy.height,
            false
        )
        val bitmap = BinaryBitmap(HybridBinarizer(source))
        try {
            val result = reader.decode(bitmap)
            qrCodeListener.invoke(result.text)
        } catch (e: Exception) {
            println("============================ UnknownException: ${e.message} ==========================")
        } finally {
            imageProxy.close()
        }
    }
}

项目依赖配置

implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.9.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.mediarouter:mediarouter:1.6.0")
implementation("androidx.camera:camera-camera2:1.3.3")
implementation("androidx.camera:camera-view:1.3.3")
implementation("androidx.camera:camera-lifecycle:1.3.3")
implementation("com.google.zxing:core:3.5.3")
implementation("com.squareup.okhttp3:okhttp:4.9.1")
implementation("com.google.code.gson:gson:2.8.8")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

错误日志

2024-05-07 16:04:09.897 13229-13305 AndroidRuntime          com.example.cardapionamesa2          E  FATAL EXCEPTION: pool-3-thread-1
                                                                                                    Process: com.example.cardapionamesa2, PID: 13229
                                                                                                    java.lang.Error: com.google.zxing.NotFoundException
                                                                                                        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1173)
                                                                                                        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
                                                                                                        at java.lang.Thread.run(Thread.java:920)
                                                                                                    Caused by: com.google.zxing.NotFoundException

解决方案

针对不同设备图像格式、数据排列差异导致的Zxing解析失败,可从以下几个方向逐一修复:

1. 修正YUV图像数据的Stride处理

不同设备的YUV图像平面行间距(stride)可能大于图像宽度,直接读取整个buffer会包含无效填充数据,导致图像变形。修改analyze方法的数据读取逻辑:

override fun analyze(imageProxy: ImageProxy) {
    val plane = imageProxy.planes[0]
    val buffer = plane.buffer
    val stride = plane.rowStride
    val width = imageProxy.width
    val height = imageProxy.height

    // 只提取有效图像区域的字节数据
    val data = ByteArray(width * height)
    var bufferPos = 0
    var dataPos = 0

    // 逐行复制,跳过每行末尾的填充字节
    for (row in 0 until height) {
        buffer.position(bufferPos)
        buffer.get(data, dataPos, width)
        bufferPos += stride
        dataPos += width
    }

    val source = com.google.zxing.PlanarYUVLuminanceSource(
        data,
        width,
        height,
        0,
        0,
        width,
        height,
        false
    )
    val bitmap = BinaryBitmap(HybridBinarizer(source))
    
    try {
        val result = reader.decode(bitmap)
        qrCodeListener.invoke(result.text)
    } catch (e: com.google.zxing.NotFoundException) {
        // 正常解析失败,无需冗余日志
    } catch (e: Exception) {
        android.util.Log.e("QRCodeAnalyzer", "解析异常: ${e.message}")
    } finally {
        imageProxy.close()
    }
}

2. 优化Zxing解析配置

默认MultiFormatReader未启用复杂解析策略,添加格式指定与重试逻辑:

import com.google.zxing.DecodeHintType
import com.google.zxing.BarcodeFormat

private val reader: MultiFormatReader = MultiFormatReader().apply {
    val hints = mapOf(
        DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE), // 指定只解析QR码,提升效率
        DecodeHintType.TRY_HARDER to true, // 启用高精度解析模式
        DecodeHintType.CHARACTER_SET to "UTF-8" // 指定字符集
    )
    setHints(hints)
}

// 在analyze方法中添加图像旋转重试
try {
    var result = reader.decode(bitmap)
    // 首次失败尝试旋转图像(适配摄像头方向与屏幕方向不一致的设备)
    if (result == null) {
        val rotatedSource = source.rotateCounterClockwise()
        val rotatedBitmap = BinaryBitmap(HybridBinarizer(rotatedSource))
        result = reader.decode(rotatedBitmap)
    }
    qrCodeListener.invoke(result.text)
} catch (e: com.google.zxing.NotFoundException) {
    // 忽略正常解析失败
} catch (e: Exception) {
    android.util.Log.e("QRCodeAnalyzer", "解析异常: ${e.message}")
}

3. 调整CameraX图像分析配置

确保输出图像格式为Zxing支持的YUV_420_888,并设置合适的分辨率:

import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Size
import java.util.concurrent.Executors

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720)) // 平衡解析速度与精度
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) // 只处理最新帧,避免堆积
    .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888) // 指定YUV格式
    .build()

// 绑定分析器
imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), QRCodeAnalyzer { result ->
    // 处理解析结果
})

4. 适配摄像头传感器方向

部分设备摄像头传感器方向与屏幕方向不一致,导致图像旋转后无法解析。添加方向适配逻辑:

// 在初始化CameraX时获取传感器旋转角度
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
val cameraProvider = ProcessCameraProvider.getInstance(context).get()
val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, imageAnalysis)
val rotationDegrees = camera.cameraInfo.sensorRotationDegrees

// 在analyze方法中根据旋转角度调整图像源
val rotatedSource = when (rotationDegrees) {
    90 -> source.rotateCounterClockwise()
    180 -> source.rotateCounterClockwise().rotateCounterClockwise()
    270 -> source.rotateCounterClockwise().rotateCounterClockwise().rotateCounterClockwise()
    else -> source
}
val bitmap = BinaryBitmap(HybridBinarizer(rotatedSource))

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 10:35:00