二维码扫描zxing异常ArrayIndexOutOfBoundsException解决

二维码扫描zxing在某一些设备上会出现ArrayIndexOutOfBoundsException异常,异常信息类似:

1
java.lang.ArrayIndexOutOfBoundsException: length=629856; index=921599

或着

1
2
3
4
5
6
7
8
9
10
11
12
13
java.lang.ArrayIndexOutOfBoundsException: src.length=629856 srcPos=630000 dst.length=360000 dstPos=286400 length=800
at java.lang.System.arraycopy(Native Method)
at com.google.zxing.PlanarYUVLuminanceSource.getMatrix(PlanarYUVLuminanceSource.java:102)
at com.google.zxing.common.HybridBinarizer.getBlackMatrix(HybridBinarizer.java:70)
at com.google.zxing.BinaryBitmap.getBlackMatrix(BinaryBitmap.java:85)
at com.google.zxing.qrcode.QRCodeReader.decode(QRCodeReader.java:77)
at com.google.zxing.MultiFormatReader.decodeInternal(MultiFormatReader.java:171)
at com.google.zxing.MultiFormatReader.decodeWithState(MultiFormatReader.java:85)
at com.google.zxing.client.android.DecodeHandler.decode(DecodeHandler.java:82)
at com.google.zxing.client.android.DecodeHandler.handleMessage(DecodeHandler.java:58)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at com.google.zxing.client.android.DecodeThread.run(DecodeThread.java:110)

版本不同,异常信息或许有一些差别。

在我这里出现异常的手机是一部android4.2的,屏幕是960×540

通过比较不同的设备,从zxing的log中发现了一些不同之处,下面这段是这部出现异常的手机的log

1
2
3
4
5
I/com.google.zxing.client.android.camera.open.OpenCameraInterface(4212): Opening camera #0
I/CameraConfiguration(4212): Screen resolution: Point(960, 540)
I/CameraConfiguration(4212): Supported preview sizes: 1280x720 848x480 720x480 640x480 352x288 320x240 176x144
I/CameraConfiguration(4212): Using largest suitable preview size: Point(1280, 720)
I/CameraConfiguration(4212): Camera resolution: Point(1280, 720)

zxing设置照相机的分辨率是根据屏幕的大小(Screen resolution)在照相机支持的sizes中找到的,
从上面这段log中看出来手机屏幕只有960×540却最终使用了 1280×720的分辨率,所以在取景的时候出现了ArrayIndexOutOfBoundsException
在其他正常运行的设备中看这段log基本都是Camera resolution比Screen resolution小一些,例如下面这一段

1
2
3
4
5
6
I/GingerbreadOpenCamera(7328): Opening camera #0
I/CameraConfiguration(7328): Screen resolution: Point(768, 1184)
I/CameraConfiguration(7328): Supported preview sizes: 1280x960 1280x720 800x480 720x480 640x480 576x432 480x320 384x288 352x288 320x240 240x160 176x144
I/CameraConfiguration(7328): Found best approximate preview size: Point(720, 480)
I/CameraConfiguration(7328): Camera resolution: Point(720, 480)
I/CameraConfiguration(7328): Initial camera parameters: ae-bracket-hdr=

因此我们需要修改程序让他滤过这个比screen resolution大的sizes

最终在类CameraConfigurationManager中发现了findBestPreviewSizeValue是用来找这个sizes的方法,那么我们就是要修改这个方法了。
今天发现zxing有一些版本中这个方法不再CameraConfigurationManager中,而是在CameraConfigurationUtils这个类中

有一段代码

1
2
3
4
5
if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) {
Point exactPoint = new Point(realWidth, realHeight);
Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint);
return exactPoint;
}

这一段是找到了一个最合适的size,就是 support size 等于 screen size。我们要改的代码在这段代码附近

1
2
3
4
5
6
float aspectRatio = (float) maybeFlippedWidth/ (float) maybeFlippedHeight;
float newDiff = Math.abs(aspectRatio - screenAspectRatio);
if (newDiff < diff) {
bestSize = new Point(realWidth, realHeight);
diff = newDiff;
}

我们给他加一个条件过滤掉比 screen 大的size

1
2
3
4
5
6
7
8
float aspectRatio = (float) maybeFlippedWidth/ (float) maybeFlippedHeight;
float newDiff = Math.abs(aspectRatio - screenAspectRatio);
//保证宽高小于屏幕的宽高
boolean isSmaller = maybeFlippedWidth<screenResolution.y && maybeFlippedHeight <screenResolution.x;
if (newDiff < diff && isSmaller) {
bestSize = new Point(realWidth, realHeight);
diff = newDiff;
}

今天看了下最新版zxing的代码,算法有变动已经没有上面的这一段代码了,但是依然还是有这个异常出现。因此还是要过滤

在exactPoint查找的后面,在while循环的末尾加一段代码过滤掉比screen大的size

1
2
3
if(maybeFlippedWidth > screenResolution.x && maybeFlippedHeight > screenResolution.y) {
it.remove();
}

后记

我这里的扫描二维码用的是竖屏的,上面所说的新版zxing代码改动的是在横屏的基础上修改的
其他人如果也遇到这个问题,在修改时,需要留意屏幕的方向,代码不一定跟这个是一样的
不知道这种把大于screen的size过滤掉的方法是不是最好的,但是这样改之后,程序是可以正常运行了,其他手机上也正常,如果谁有更好的解决方案,请留言告诉我~谢谢!