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用于调用摄像头。
摄像头的初始化:
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进行识别操作:
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函数:
@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)来处理返回到这层的识别的数据:
/**
* 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) {
这其中,处理又有两种方式:内部方式和外部方式。
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上做显示:
// Put up our own UI for how to handle the decoded contents.
private void handleDecodeInternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode) {
而handleDecodeExternally则进一步往上一层传,如果外部有调用的话:
// 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:
CaptureActivityHandler(CaptureActivity activity,
Collection decodeFormats,
Map 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方法中获取返回的信息:
@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:
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的方法:
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的内容:
DecodeThread(CaptureActivity activity,
Collection decodeFormats,
Map 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:
@Override
public void run() {
Looper.prepare();
handler = new DecodeHandler(activity, hints);
handlerInitLatch.countDown();
Looper.loop();
}
DecodeHandler.java:
DecodeHandler继承Handler,在handleMessage里调用该class里的decode函数:
@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里:
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支持读取多种格式的码的内容,我们只看二维码部分:
if (formats.contains(BarcodeFormat.QR_CODE)) {
readers.add(new QRCodeReader());
}
new一个QRCodeReader()实例,解码在decodeInternal里调用reader.decode(image, hints):
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
public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException中被调用:
@Override
public final Result decode(BinaryBitmap image, Map 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 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;
}
可以看到该函数先检测图片中的二维码,检测到之后再进行解码:
检测二维码:
DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
解码二维码:
decoderResult = decoder.decode(detectorResult.getBits(), hints);
在这里我们可以看到上层得到的rawResult里包含的是什么内容:
Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE);
points为Detector检测到的二维码的位置信息:
points = detectorResult.getPoints();
在QRCodeReader.java里还有一个特别的函数,对一张干净的二维码图片进行快速解码,得到bits信息:
/**
* 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
private DecoderResult decode(BitMatrixParser parser, Map
/**
* Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.
*
* @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 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 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里面存放的位置信息的结构:
if (alignmentPattern == null) {
points = new ResultPoint[]{bottomLeft, topLeft, topRight};
} else {
points = new ResultPoint[]{bottomLeft, topLeft, topRight, alignmentPattern};
}
bottomLeft, topLeft, topRight为二维码的三个定位点:
FinderPattern topLeft = info.getTopLeft();
FinderPattern topRight = info.getTopRight();
FinderPattern bottomLeft = info.getBottomLeft();
对FinderPattern的定义在FinderPattern.java里:
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类型的。