目前市面上二维码的扫描似乎用开源google的zxing比较多,接下去以2.2版本做一个简析吧,勿喷。。。
下载下来后定位两个文件夹,core和android,core是一些核心的库,android是针对android的一些代码。
我们先看核心库,在package com.google.zxing中的一些生成二维码的类关系
接口Writer里面有两个encode的重载函数,不同的格式的二维码有各自的类实现了Writer接口,MultiformatWriter类比较特殊,根据代码的注释可见其其实是个工厂类,根据BarcodeFormat实例化不同的Writer,然后最终调用各自的Encode.encode()方法
1 public final class MultiFormatWriter implements Writer { 2 3 @Override 4 public BitMatrix encode(String contents, 5 BarcodeFormat format, 6 int width, 7 int height) throws WriterException { 8 return encode(contents, format, width, height, null); 9 } 10 11 @Override 12 public BitMatrix encode(String contents, 13 BarcodeFormat format, 14 int width, int height, 15 Map<EncodeHintType,?> hints) throws WriterException { 16 17 Writer writer; 18 switch (format) { 19 case EAN_8: 20 writer = new EAN8Writer(); 21 break; 22 case EAN_13: 23 writer = new EAN13Writer(); 24 break; 25 case UPC_A: 26 writer = new UPCAWriter(); 27 break; 28 case QR_CODE: 29 writer = new QRCodeWriter(); 30 break; 31 case CODE_39: 32 writer = new Code39Writer(); 33 break; 34 case CODE_128: 35 writer = new Code128Writer(); 36 break; 37 case ITF: 38 writer = new ITFWriter(); 39 break; 40 case PDF_417: 41 writer = new PDF417Writer(); 42 break; 43 case CODABAR: 44 writer = new CodaBarWriter(); 45 break; 46 case DATA_MATRIX: 47 writer = new DataMatrixWriter(); 48 break; 49 case AZTEC: 50 writer = new AztecWriter(); 51 break; 52 default: 53 throw new IllegalArgumentException("No encoder available for format " + format); 54 } 55 return writer.encode(contents, format, width, height, hints); 56 } 57 58 }
然后看解析二维码的类结构
关键就是这个MultiformatReader,里面聚合了多个reader,并且根据客户端设置的DecodeHintType值,确定添加reader以及添加reader的顺序,最后调用reader.decode方法
1 public final class MultiFormatReader implements Reader { 2 3 private Map<DecodeHintType,?> hints; 4 private Reader[] readers; 5 6 @Override 7 public Result decode(BinaryBitmap image) throws NotFoundException { 8 setHints(null); 9 return decodeInternal(image); 10 } 11 12 @Override 13 public Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints) throws NotFoundException { 14 setHints(hints); 15 return decodeInternal(image); 16 } 17 18 public Result decodeWithState(BinaryBitmap image) throws NotFoundException { 19 // Make sure to set up the default state so we don't crash 20 if (readers == null) { 21 setHints(null); 22 } 23 return decodeInternal(image); 24 } 25 26 public void setHints(Map<DecodeHintType,?> hints) {//根据设置的hint来设置reader 27 this.hints = hints; 28 29 boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER); 30 @SuppressWarnings("unchecked") 31 Collection<BarcodeFormat> formats = 32 hints == null ? null : (Collection<BarcodeFormat>) hints.get(DecodeHintType.POSSIBLE_FORMATS); 33 Collection<Reader> readers = new ArrayList<Reader>(); 34 if (formats != null) { 35 boolean addOneDReader = 36 formats.contains(BarcodeFormat.UPC_A) || 37 formats.contains(BarcodeFormat.UPC_E) || 38 formats.contains(BarcodeFormat.EAN_13) || 39 formats.contains(BarcodeFormat.EAN_8) || 40 formats.contains(BarcodeFormat.CODABAR) || 41 formats.contains(BarcodeFormat.CODE_39) || 42 formats.contains(BarcodeFormat.CODE_93) || 43 formats.contains(BarcodeFormat.CODE_128) || 44 formats.contains(BarcodeFormat.ITF) || 45 formats.contains(BarcodeFormat.RSS_14) || 46 formats.contains(BarcodeFormat.RSS_EXPANDED); 47 // Put 1D readers upfront in "normal" mode 48 if (addOneDReader && !tryHarder) { 49 readers.add(new MultiFormatOneDReader(hints)); 50 } 51 if (formats.contains(BarcodeFormat.QR_CODE)) { 52 readers.add(new QRCodeReader()); 53 } 54 if (formats.contains(BarcodeFormat.DATA_MATRIX)) { 55 readers.add(new DataMatrixReader()); 56 } 57 if (formats.contains(BarcodeFormat.AZTEC)) { 58 readers.add(new AztecReader()); 59 } 60 if (formats.contains(BarcodeFormat.PDF_417)) { 61 readers.add(new PDF417Reader()); 62 } 63 if (formats.contains(BarcodeFormat.MAXICODE)) { 64 readers.add(new MaxiCodeReader()); 65 } 66 // At end in "try harder" mode 67 if (addOneDReader && tryHarder) { 68 readers.add(new MultiFormatOneDReader(hints)); 69 } 70 } 71 if (readers.isEmpty()) { 72 if (!tryHarder) { 73 readers.add(new MultiFormatOneDReader(hints)); 74 } 75 76 readers.add(new QRCodeReader()); 77 readers.add(new DataMatrixReader()); 78 readers.add(new AztecReader()); 79 readers.add(new PDF417Reader()); 80 readers.add(new MaxiCodeReader()); 81 82 if (tryHarder) { 83 readers.add(new MultiFormatOneDReader(hints)); 84 } 85 } 86 this.readers = readers.toArray(new Reader[readers.size()]); 87 } 88 89 @Override 90 public void reset() { 91 if (readers != null) { 92 for (Reader reader : readers) { 93 reader.reset(); 94 } 95 } 96 } 97 98 private Result decodeInternal(BinaryBitmap image) throws NotFoundException {//最终都调用这个方法 99 if (readers != null) { 100 for (Reader reader : readers) { 101 try { 102 return reader.decode(image, hints); 103 } catch (ReaderException re) { 104 // continue 105 } 106 } 107 } 108 throw NotFoundException.getNotFoundInstance(); 109 } 110 111 }
DecodeHintType的语法比较有意思,还在理解中
1 public enum DecodeHintType { 2 3 /** 4 * Unspecified, application-specific hint. Maps to an unspecified {@link Object}. 5 */ 6 OTHER(Object.class), 7 8 /** 9 * Image is a pure monochrome image of a barcode. Doesn't matter what it maps to; 10 * use {@link Boolean#TRUE}. 11 */ 12 PURE_BARCODE(Void.class), 13 14 /** 15 * Image is known to be of one of a few possible formats. 16 * Maps to a {@link List} of {@link BarcodeFormat}s. 17 */ 18 POSSIBLE_FORMATS(List.class), 19 20 /** 21 * Spend more time to try to find a barcode; optimize for accuracy, not speed. 22 * Doesn't matter what it maps to; use {@link Boolean#TRUE}. 23 */ 24 TRY_HARDER(Void.class), 25 26 /** 27 * Specifies what character encoding to use when decoding, where applicable (type String) 28 */ 29 CHARACTER_SET(String.class), 30 31 /** 32 * Allowed lengths of encoded data -- reject anything else. Maps to an {@code int[]}. 33 */ 34 ALLOWED_LENGTHS(int[].class), 35 36 /** 37 * Assume Code 39 codes employ a check digit. Doesn't matter what it maps to; 38 * use {@link Boolean#TRUE}. 39 */ 40 ASSUME_CODE_39_CHECK_DIGIT(Void.class), 41 42 /** 43 * Assume the barcode is being processed as a GS1 barcode, and modify behavior as needed. 44 * For example this affects FNC1 handling for Code 128 (aka GS1-128). Doesn't matter what it maps to; 45 * use {@link Boolean#TRUE}. 46 */ 47 ASSUME_GS1(Void.class), 48 49 /** 50 * The caller needs to be notified via callback when a possible {@link ResultPoint} 51 * is found. Maps to a {@link ResultPointCallback}. 52 */ 53 NEED_RESULT_POINT_CALLBACK(ResultPointCallback.class), 54 55 // End of enumeration values. 56 ; 57 58 /** 59 * Data type the hint is expecting. 60 * Among the possible values the {@link Void} stands out as being used for 61 * hints that do not expect a value to be supplied (flag hints). Such hints 62 * will possibly have their value ignored, or replaced by a 63 * {@link Boolean#TRUE}. Hint suppliers should probably use 64 * {@link Boolean#TRUE} as directed by the actual hint documentation. 65 */ 66 private final Class<?> valueType; 67 68 DecodeHintType(Class<?> valueType) { 69 this.valueType = valueType; 70 } 71 72 public Class<?> getValueType() { 73 return valueType; 74 } 75 76 }
然后我们看下android里面是如何调用的,入口是CaptureActivity,在com.google.zxing.client.android package中,以下描述一个通用的流程
CaptureAct中的onResume中的initCamera初始化CaptureActHandler,其构造函数中新起了一个DecodeThread去异步准备一个DecodeHandler,然后调用restartPreviewAndDecode方法,让DecodeHandler去处理R.id.decode的消息,当然这里需要处理一些线程同步问题,代码里用到了CountDownLatch来控制。DecodeHanlder处理R.id.decode消息后用传递R.id.decode_succeeded消息给CaptureActHanlder,最终再调用handleDecode传递给CaptureAct.