分析google的Zxing条码扫描库源码

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);
安卓层扫码大致流程如下图所示:


分析google的Zxing条码扫描库源码_第1张图片
安卓层扫码大致流程

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 hints),在
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 hints)和
private DecoderResult decode(BitMatrixParser parser, Map hints):

  /**
   * 

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类型的。

你可能感兴趣的:(分析google的Zxing条码扫描库源码)