ZXing是一个开放源码的,用Java实现的多种格式的条码扫描与生成库。
Zxing的GITHUB地址:Zxing

ZXing支持的格式:

1D product 1D industrial 2D
UPC-A Code 39 QR Code
UPC-E Code 93 Data Matrix
EAN-8 Code 128 Aztec (beta)
EAN-13 Codabar PDF 417 (beta)
ITF MaxiCode
RSS-14
RSS-Expanded

zxing代码库结构及其介绍如下:

Module Description
core The core image decoding library, and test code
javase JavaSE-specific client code
android Android client Barcode Scanner
android-integration Supports integration with Barcode Scanner via Intent
android-core Android-related code shared among android, other Android apps
zxingorg The source behind zxing.org
zxing.appspot.com The source behind web-based barcode generator at zxing.appspot.com

这篇文章主要分析android上是如何调用zxing去识别二维码的。
先在GITHUB上下载源码,目前最新的版本是3.3.3。解压并打开文件夹,我们主要看android、android-core和core这三个文件夹。

android文件夹:

在“zxing-zxing-3.3.3\android\src\com\google\zxing\client\android”下有android的src代码。

CaptureActivity.java

主界面是CaptureActivity.java。在CaptureActivity.java中,cameraManager用于调用摄像头。
摄像头的初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void initCamera(SurfaceHolder surfaceHolder) {
if (surfaceHolder == null) {
throw new IllegalStateException("No SurfaceHolder provided");
}
if (cameraManager.isOpen()) {
Log.w(TAG, "initCamera() while already open -- late SurfaceView callback?");
return;
}
try {
cameraManager.openDriver(surfaceHolder);
// Creating the handler starts the preview, which can also throw a RuntimeException.
if (handler == null) {
handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);
}
decodeOrStoreSavedBitmap(null, null);
} catch (IOException ioe) {
Log.w(TAG, ioe);
displayFrameworkBugMessageAndExit();
} catch (RuntimeException e) {
// Barcode Scanner has seen crashes in the wild of this variety:
// java.?lang.?RuntimeException: Fail to connect to camera service
Log.w(TAG, "Unexpected error initializing camera", e);
displayFrameworkBugMessageAndExit();
}
}

可以看到在打开摄像头后,即调用CaptureActivityHandler进行识别操作:

1
2
3
4
5
cameraManager.openDriver(surfaceHolder);
// Creating the handler starts the preview, which can also throw a RuntimeException.
if (handler == null) {
handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);
}

摄像头预览的内容是放在名为preview_view的SurfaceView容器里的。CaptureActivity extends Activity implements SurfaceHolder.Callback并重写了这三个Surface的Callback函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (holder == null) {
Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!");
}
if (!hasSurface) {
hasSurface = true;
initCamera(holder);
}
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
hasSurface = false;
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// do nothing
}

使用 public void handleDecode(Result rawResult, Bitmap barcode, float scaleFactor)来处理返回到这层的识别的数据:

1
2
3
4
5
6
7
8
/**
* A valid barcode has been found, so give an indication of success and show the results.
*
* @param rawResult The contents of the barcode.
* @param scaleFactor amount by which thumbnail was scaled
* @param barcode A greyscale bitmap of the camera data which was decoded.
*/
public void handleDecode(Result rawResult, Bitmap barcode, float scaleFactor) {

这其中,处理又有两种方式:内部方式和外部方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
switch (source) {
case NATIVE_APP_INTENT:
case PRODUCT_SEARCH_LINK:
handleDecodeExternally(rawResult, resultHandler, barcode);
break;
case ZXING_LINK:
if (scanFromWebPageManager == null || !scanFromWebPageManager.isScanFromWebPage()) {
handleDecodeInternally(rawResult, resultHandler, barcode);
} else {
handleDecodeExternally(rawResult, resultHandler, barcode);
}
break;
case NONE:
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
if (fromLiveScan && prefs.getBoolean(PreferencesActivity.KEY_BULK_MODE, false)) {
Toast.makeText(getApplicationContext(),
getResources().getString(R.string.msg_bulk_mode_scanned) + " (" + rawResult.getText() + ')',
Toast.LENGTH_SHORT).show();
maybeSetClipboard(resultHandler);
// Wait a moment or else it will scan the same barcode continuously about 3 times
restartPreviewAfterDelay(BULK_MODE_SCAN_DELAY_MS);
} else {
handleDecodeInternally(rawResult, resultHandler, barcode);
}
break;+
}

handleDecodeInternally主要是在UI上做显示:

1
2
// Put up our own UI for how to handle the decoded contents.
private void handleDecodeInternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode) {

而handleDecodeExternally则进一步往上一层传,如果外部有调用的话:

1
2
// Briefly show the contents of the barcode, then handle the result outside Barcode Scanner.
private void handleDecodeExternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode) {

CaptureActivityHandler.java

在CaptureActivity调用了CaptureActivityHandler后,参数仅一步传递到DecodeThread:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CaptureActivityHandler(CaptureActivity activity,
Collection<BarcodeFormat> decodeFormats,
Map<DecodeHintType,?> baseHints,
String characterSet,
CameraManager cameraManager) {
this.activity = activity;
decodeThread = new DecodeThread(activity, decodeFormats, baseHints, characterSet,
new ViewfinderResultPointCallback(activity.getViewfinderView()));
decodeThread.start();
state = State.SUCCESS;

// Start ourselves capturing previews and decoding.
this.cameraManager = cameraManager;
cameraManager.startPreview();
restartPreviewAndDecode();
}

此外,CaptureActivityHandler继承了handle,并重写了handleMessage方法,从而使用CaptureActivityHandler来为CaptureActivity处理底层的消息。调用过程:
在CaptureActivity的onActivityResult方法中获取返回的信息:

1
2
3
4
5
6
7
8
9
10
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (resultCode == RESULT_OK && requestCode == HISTORY_REQUEST_CODE && historyManager != null) {
int itemNumber = intent.getIntExtra(Intents.History.ITEM_NUMBER, -1);
if (itemNumber >= 0) {
HistoryItem historyItem = historyManager.buildHistoryItem(itemNumber);
decodeOrStoreSavedBitmap(null, historyItem.getResult());
}
}
}

这其中,返回的信息被保存至历史记录,并在decodeOrStoreSavedBitmap函数里将消息传递到CaptureActivityHandler的handleMessage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void decodeOrStoreSavedBitmap(Bitmap bitmap, Result result) {
// Bitmap isn't used yet -- will be used soon
if (handler == null) {
savedResultToShow = result;
} else {
if (result != null) {
savedResultToShow = result;
}
if (savedResultToShow != null) {
Message message = Message.obtain(handler, R.id.decode_succeeded, savedResultToShow);
handler.sendMessage(message);
}
savedResultToShow = null;
}
}

在handleMessage里调用了前面所说的CaptureActivity里的handleDecode的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
case R.id.decode_succeeded:
state = State.SUCCESS;
Bundle bundle = message.getData();
Bitmap barcode = null;
float scaleFactor = 1.0f;
if (bundle != null) {
byte[] compressedBitmap = bundle.getByteArray(DecodeThread.BARCODE_BITMAP);
if (compressedBitmap != null) {
barcode = BitmapFactory.decodeByteArray(compressedBitmap, 0, compressedBitmap.length, null);
// Mutable copy:
barcode = barcode.copy(Bitmap.Config.ARGB_8888, true);
}
scaleFactor = bundle.getFloat(DecodeThread.BARCODE_SCALED_FACTOR);
}
activity.handleDecode((Result) message.obj, barcode, scaleFactor);
break;

DecodeThread.java:

DecodeThread是一个线程,继承了Thread。
其初始化里添加了decodeFormats的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
DecodeThread(CaptureActivity activity,
Collection<BarcodeFormat> decodeFormats,
Map<DecodeHintType,?> baseHints,
String characterSet,
ResultPointCallback resultPointCallback) {

this.activity = activity;
handlerInitLatch = new CountDownLatch(1);

hints = new EnumMap<>(DecodeHintType.class);
if (baseHints != null) {
hints.putAll(baseHints);
}

// The prefs can't change while the thread is running, so pick them up once here.
if (decodeFormats == null || decodeFormats.isEmpty()) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
decodeFormats = EnumSet.noneOf(BarcodeFormat.class);
if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_1D_PRODUCT, true)) {
decodeFormats.addAll(DecodeFormatManager.PRODUCT_FORMATS);
}
if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_1D_INDUSTRIAL, true)) {
decodeFormats.addAll(DecodeFormatManager.INDUSTRIAL_FORMATS);
}
if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_QR, true)) {
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
}
if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_DATA_MATRIX, true)) {
decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
}
if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_AZTEC, false)) {
decodeFormats.addAll(DecodeFormatManager.AZTEC_FORMATS);
}
if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_PDF417, false)) {
decodeFormats.addAll(DecodeFormatManager.PDF417_FORMATS);
}
}
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);

if (characterSet != null) {
hints.put(DecodeHintType.CHARACTER_SET, characterSet);
}
hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
Log.i("DecodeThread", "Hints: " + hints);
}

在其run方法里通向下一层调用的方法DecodeHandler:

1
2
3
4
5
6
7
@Override
public void run() {
Looper.prepare();
handler = new DecodeHandler(activity, hints);
handlerInitLatch.countDown();
Looper.loop();
}

DecodeHandler.java:

DecodeHandler继承Handler,在handleMessage里调用该class里的decode函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void handleMessage(Message message) {
if (message == null || !running) {
return;
}
switch (message.what) {
case R.id.decode:
decode((byte[]) message.obj, message.arg1, message.arg2);
break;
case R.id.quit:
running = false;
Looper.myLooper().quit();
break;
}
}

decode函数完成了解码和解码结果的数据回传,android的调用core的解码的接口就在decode里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private void decode(byte[] data, int width, int height) {
long start = System.currentTimeMillis();
Result rawResult = null;
PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
if (source != null) {
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
rawResult = multiFormatReader.decodeWithState(bitmap);
} catch (ReaderException re) {
// continue
} finally {
multiFormatReader.reset();
}
}

Handler handler = activity.getHandler();
if (rawResult != null) {
// Don't log the barcode contents for security.
long end = System.currentTimeMillis();
Log.d(TAG, "Found barcode in " + (end - start) + " ms");
if (handler != null) {
Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult);
Bundle bundle = new Bundle();
bundleThumbnail(source, bundle);
message.setData(bundle);
message.sendToTarget();
}
} else {
if (handler != null) {
Message message = Message.obtain(handler, R.id.decode_failed);
message.sendToTarget();
}
}
}

decode调用的底层函数为:rawResult = multiFormatReader.decodeWithState(bitmap);
安卓层扫码大致流程如下图所示:
安卓层扫码大致流程

core文件夹:

在“zxing-zxing-3.3.3\core\src\main\java\com\google\zxing”下有core的src代码。

MultiFormatReader.java

MultiFormatReader支持读取多种格式的码的内容,我们只看二维码部分:

1
2
3
if (formats.contains(BarcodeFormat.QR_CODE)) {
readers.add(new QRCodeReader());
}

new一个QRCodeReader()实例,解码在decodeInternal里调用reader.decode(image, hints):

1
2
3
4
5
6
7
8
9
10
11
12
private Result decodeInternal(BinaryBitmap image) throws NotFoundException {
if (readers != null) {
for (Reader reader : readers) {
try {
return reader.decode(image, hints);
} catch (ReaderException re) {
// continue
}
}
}
throw NotFoundException.getNotFoundInstance();
}

QRCodeReader.java

QRCodeReader.java在“zxing-zxing-3.3.3\core\src\main\java\com\google\zxing\qrcode”文件夹下:
实现解码的函数为 public final Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints),在
public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException中被调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Override
public final Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
throws NotFoundException, ChecksumException, FormatException {
DecoderResult decoderResult;
ResultPoint[] points;
if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
BitMatrix bits = extractPureBits(image.getBlackMatrix());
decoderResult = decoder.decode(bits, hints);
points = NO_POINTS;
} else {
DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
decoderResult = decoder.decode(detectorResult.getBits(), hints);
points = detectorResult.getPoints();
}

// If the code was mirrored: swap the bottom-left and the top-right points.
if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
}

Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE);
List<byte[]> byteSegments = decoderResult.getByteSegments();
if (byteSegments != null) {
result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
}
String ecLevel = decoderResult.getECLevel();
if (ecLevel != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
}
if (decoderResult.hasStructuredAppend()) {
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
decoderResult.getStructuredAppendSequenceNumber());
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
decoderResult.getStructuredAppendParity());
}
return result;
}

可以看到该函数先检测图片中的二维码,检测到之后再进行解码:
检测二维码:

1
DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);

解码二维码:

1
decoderResult = decoder.decode(detectorResult.getBits(), hints);

在这里我们可以看到上层得到的rawResult里包含的是什么内容:

1
Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE);

points为Detector检测到的二维码的位置信息:

1
points = detectorResult.getPoints();

在QRCodeReader.java里还有一个特别的函数,对一张干净的二维码图片进行快速解码,得到bits信息:

1
2
3
4
5
6
7
8
9
/**
* This method detects a code in a "pure" image -- that is, pure monochrome image
* which contains only an unrotated, unskewed, image of a code, with some white border
* around it. This is a specialized method that works exceptionally fast in this special
* case.
*
* @see com.google.zxing.datamatrix.DataMatrixReader#extractPureBits(BitMatrix)
*/
private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException

Decoder.java:

Decoder.java在“zxing-zxing-3.3.3\core\src\main\java\com\google\zxing\qrcode\decoder”文件夹下:
主要的解码函数是private DecoderResult decode(BitMatrix bits, Map<DecodeHintType,?> hints)和
private DecoderResult decode(BitMatrixParser parser, Map<DecodeHintType,?> hints):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
* <p>Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.</p>
*
* @param bits booleans representing white/black QR Code modules
* @param hints decoding hints that should be used to influence decoding
* @return text and bytes encoded within the QR Code
* @throws FormatException if the QR Code cannot be decoded
* @throws ChecksumException if error correction fails
*/
public DecoderResult decode(BitMatrix bits, Map<DecodeHintType,?> hints)
throws FormatException, ChecksumException {

// Construct a parser and read version, error-correction level
BitMatrixParser parser = new BitMatrixParser(bits);
FormatException fe = null;
ChecksumException ce = null;
try {
return decode(parser, hints);
} catch (FormatException e) {
fe = e;
} catch (ChecksumException e) {
ce = e;
}

try {

// Revert the bit matrix
parser.remask();

// Will be attempting a mirrored reading of the version and format info.
parser.setMirror(true);

// Preemptively read the version.
parser.readVersion();

// Preemptively read the format information.
parser.readFormatInformation();

/*
* Since we're here, this means we have successfully detected some kind
* of version and format information when mirrored. This is a good sign,
* that the QR code may be mirrored, and we should try once more with a
* mirrored content.
*/
// Prepare for a mirrored reading.
parser.mirror();

DecoderResult result = decode(parser, hints);

// Success! Notify the caller that the code was mirrored.
result.setOther(new QRCodeDecoderMetaData(true));

return result;

} catch (FormatException | ChecksumException e) {
// Throw the exception from the original reading
if (fe != null) {
throw fe;
}
throw ce; // If fe is null, this can't be
}
}

private DecoderResult decode(BitMatrixParser parser, Map<DecodeHintType,?> hints)
throws FormatException, ChecksumException {
Version version = parser.readVersion();
ErrorCorrectionLevel ecLevel = parser.readFormatInformation().getErrorCorrectionLevel();

// Read codewords
byte[] codewords = parser.readCodewords();
// Separate into data blocks
DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel);

// Count total number of data bytes
int totalBytes = 0;
for (DataBlock dataBlock : dataBlocks) {
totalBytes += dataBlock.getNumDataCodewords();
}
byte[] resultBytes = new byte[totalBytes];
int resultOffset = 0;

// Error-correct and copy data blocks together into a stream of bytes
for (DataBlock dataBlock : dataBlocks) {
byte[] codewordBytes = dataBlock.getCodewords();
int numDataCodewords = dataBlock.getNumDataCodewords();
correctErrors(codewordBytes, numDataCodewords);
for (int i = 0; i < numDataCodewords; i++) {
resultBytes[resultOffset++] = codewordBytes[i];
}
}

// Decode the contents of that stream of bytes
return DecodedBitStreamParser.decode(resultBytes, version, ecLevel, hints);
}

Detector.java:

Detector.java在“zxing-zxing-3.3.3\core\src\main\java\com\google\zxing\qrcode\detector”文件夹下:
在这里可以看到points里面存放的位置信息的结构:

1
2
3
4
5
if (alignmentPattern == null) {
points = new ResultPoint[]{bottomLeft, topLeft, topRight};
} else {
points = new ResultPoint[]{bottomLeft, topLeft, topRight, alignmentPattern};
}

bottomLeft, topLeft, topRight为二维码的三个定位点:

1
2
3
FinderPattern topLeft = info.getTopLeft();
FinderPattern topRight = info.getTopRight();
FinderPattern bottomLeft = info.getBottomLeft();

对FinderPattern的定义在FinderPattern.java里:

1
2
3
4
5
6
7
8
9
FinderPattern(float posX, float posY, float estimatedModuleSize) {
this(posX, posY, estimatedModuleSize, 1);
}

private FinderPattern(float posX, float posY, float estimatedModuleSize, int count) {
super(posX, posY);
this.estimatedModuleSize = estimatedModuleSize;
this.count = count;
}

可以看到,topLeft的x,y两个值为float类型的。