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
相关产品推荐
相关产品推荐

