Preface: the previous article has made the source code run, but there are still a lot of code irrelevant to the scanning two-dimensional code, this will delete the useless code only retain the code related to the scanning code, while analyzing the steps of decoding.

To streamline the code

The goal of this article is to analyze the steps of decoding. In order not to be disturbed by irrelevant code, the source code will be simplified and only the code related to decoding will be preserved.

The main deleted code is to identify the content of the TWO-DIMENSIONAL code, some other operations, such as sharing, record the history of scanning, search and analysis results. The structure of the deleted Android module is as follows

Source code analysis

In order to facilitate understanding and memorizing the steps of ZXing decoding, I will draw UML sequence diagram while analyzing. Finally, after analyzing the steps of decoding, there will be a complete sequence diagram. Now, start at the entrance to the main program, which is the onCreate method of CaptureActivity.

OnCreate source code analysis

The following code

public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    // The screen is long and bright when scanning
    Window window = getWindow();
    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    setContentView(R.layout.capture);

    hasSurface = false;
    // Control the activity automatically finish after a period of no action
    inactivityTimer = new InactivityTimer(this);
    // Manage whether there is sound and vibration after scanning code
    beepManager = new BeepManager(this);
    // Automatically turn the flash on or off according to the ambient light
    ambientLightManager = new AmbientLightManager(this);
    // Load some default configurations
    PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
  }
Copy the code

This method is mainly used to instantiate some objects and get configuration information. The above code has been commented, so I won’t go into details.

OnResume source code analysis

Moving on to the second method of the Activity life cycle, the code follows

  protected void onResume(a) {
    super.onResume();
    // CameraManager must be initialized here, not in onCreate(). This is necessary because we don't
    // want to open the camera driver and measure the screen size if we're going to show the help on
    // first launch. That led to bugs where the scanning rectangle was the wrong size and partially
    // off screen.
    cameraManager = new CameraManager(getApplication());/ / 1

    viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
    viewfinderView.setCameraManager(cameraManager);

    resultView = findViewById(R.id.result_view);
    statusView = (TextView) findViewById(R.id.status_view);
    handler = null;
    // omit unimportant code
    / /...
    SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
    SurfaceHolder surfaceHolder = surfaceView.getHolder();/ / 2
    if (hasSurface) {
      // The activity was paused but not stopped, so the surface still exists. Therefore
      // surfaceCreated() won't be called, so init the camera here.
      initCamera(surfaceHolder);
    } else {
      // Install the callback and wait for surfaceCreated() to init the camera.
      surfaceHolder.addCallback(this);/ / 3}}Copy the code

Some of the statements in the code above mark the ordinal. Now look at what the code at “1” does. Enter the constructor of the CameraManager class

public CameraManager(Context context) {
    this.context = context;
    this.configManager = new CameraConfigurationManager(context);/ / 1.1
    previewCallback = new PreviewCallback(configManager);/ / 1.2
  }
Copy the code

Continue to follow up the code, look at the code of the “1.1”, CameraConfigurationManager what he did in the constructor, the following code

CameraConfigurationManager(Context context) {
    this.context = context;
  }
Copy the code

The code above is injecting the context. Now look at the code at “1.2”. What does the PreviewCallback constructor do

PreviewCallback(CameraConfigurationManager configManager) {
    this.configManager = configManager;
  }
Copy the code

The above you can see, this code in PreviewCallback construction method, will CameraConfigurationManager class instances, injected into PreviewCallback class. After following the code at “1”, continue to look at the code in the onResume method. Here is the code at “2”. The SurfaceHolder function is described below

The SurfaceHolder is an interface that acts like a Surface listener. Providingaccess and control over this SurfaceView’s underlying Surface provides access and control over the SurfaceView’s underlying Surface through three callbacks. It allows us to sense the creation, destruction, or change of the Surface.

So if WE look at the code, because hasSurface is false in the onCreate method, we’re going to go to the else statement, which is the code at “3,” which is binding the Surface listener, This is the callback method that binds the Surface lifecycle to the current Activity. There are three interface methods defined in surfaceHolder.callback:

  • public void surfaceChanged(SurfaceHolder holder, int format, int width, int height); This method is called immediately when there is any structural change (format or size) to the surface.

  • public void surfaceCreated(SurfaceHolder holder); This method is called immediately after the Surface object is created.

  • public void surfaceDestroyed(SurfaceHolder holder); This method is called immediately before the Surface object is destroyed.

SurfaceCreated (surfaceCreated) will be called first when the callback is bound to the surfaceCreated method

public void surfaceCreated(SurfaceHolder holder) {
    if (holder == null) {
      Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!");
    }
    if(! hasSurface) { hasSurface =true; initCamera(holder); }}Copy the code

To follow the code, look at initCamera(holder); What does the method do as follows

private void initCamera(SurfaceHolder surfaceHolder) {
    if (surfaceHolder == null) {
      throw new IllegalStateException("No SurfaceHolder provided");
    }
    // The camera is already on
    if (cameraManager.isOpen()) {
      Log.w(TAG, "initCamera() while already open -- late SurfaceView callback?");
      return;
    }
    try {
    // Open the camera and initialize the hardware parameters
      cameraManager.openDriver(surfaceHolder);
      // instantiate a handler and start previewing.
      if (handler == null) {
        / / 3.1
        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(); }}Copy the code

The code in “3.1” instantiates a CaptureActivityHandler. Take a look at the constructor of CaptureActivityHandler, as shown below

CaptureActivityHandler(CaptureActivity activity, Collection<BarcodeFormat> decodeFormats, Map<DecodeHintType,? > baseHints, String characterSet, CameraManager cameraManager) {this.activity = activity;/ / into the activity
    // Create a new thread and start
    / / 3.1.1
    decodeThread = new DecodeThread(activity, decodeFormats, baseHints, characterSet,
        new ViewfinderResultPointCallback(activity.getViewfinderView()));
    decodeThread.start();
    state = State.SUCCESS;

    / / cameraManager
    this.cameraManager = cameraManager;
    // Requires the camera hardware to start drawing preview frames to the screen
    cameraManager.startPreview();
    // Start previewing and decoding
    / / 3.1.2
    restartPreviewAndDecode();
  }
Copy the code

The DecodeThread constructor looks like this

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);
  }
Copy the code

As you can see from the above code, the main thing in the thread constructor is to set the format of the decoding.

If you want to improve the speed of scanning code, here is a point that can be optimized, you can not set so many formats, only set and their business related to the decoding format.

We all know that when a thread runs, it calls the run method, so let’s look at the code in the run method, as follows

public void run(a) {
    Looper.prepare();
    handler = new DecodeHandler(activity, hints);
    handlerInitLatch.countDown();
    Looper.loop();
  }
Copy the code

This code instantiates a Handler in the child thread that is bound to the current thread. Go ahead and look at the DecodeHandler constructor and see what it does

DecodeHandler(CaptureActivity activity, Map<DecodeHintType,Object> hints) {
    multiFormatReader = new MultiFormatReader();
    multiFormatReader.setHints(hints);
    this.activity = activity;
  }
Copy the code

What this code does is set hints set in the thread constructor to the instantiated MultiFormatReader and inject an instance of CaptureActivity.

The MultiFormatReader class acts as a convenience class and is the main entry point for libraries for most purposes.

At this point, the following sequence diagram can be drawn

Next, examine the code at “3.1.2”, calling the method code as follows

private void restartPreviewAndDecode(a) {
    if(state == State.SUCCESS) { state = State.PREVIEW; cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode); activity.drawViewfinder(); }}Copy the code

Focus on the cameraManager. RequestPreviewFrame (decodeThread getHandler (), R.i d.d ecode); What does the requestPreviewFrame method in CameraManager do

/**
   * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
   * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
   * respectively.
   *
   * @param handler The handler to send the message to.
   * @param message The what field of the message to be sent.
   */
  public synchronized void requestPreviewFrame(Handler handler, int message) {
    OpenCamera theCamera = camera;
    if(theCamera ! =null&& previewing) { previewCallback.setHandler(handler, message); theCamera.getCamera().setOneShotPreviewCallback(previewCallback); }}Copy the code

Arg1 and message.arg2. Then the message is returned to the handler that passes it in. This method is used to parse a preview frame, which is an array of bytes, into message.obj and into message.arg1 and message.arg2. This handler is an instance of DecodeHandler. The code for the onPreviewFrame method in the PreviewCallback class is as follows

 public void onPreviewFrame(byte[] data, Camera camera) {
    Point cameraResolution = configManager.getCameraResolution();
    Handler thePreviewHandler = previewHandler;
    if(cameraResolution ! =null&& thePreviewHandler ! =null) {
      Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
          cameraResolution.y, data);
      message.sendToTarget();
      previewHandler = null;
    } else {
      Log.d(TAG, "Got preview callback, but no handler or resolution available"); }}Copy the code

As you can see, the parsed data is sent to DecodeHandler, which eventually calls the handleMessage method in the DecodeHandler class

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; }}Copy the code

The value of message.what in the code above happens to be R.id.decode, which naturally enters the decode method. At this point, take a look at the current sequence diagram, as shown below

  • To enter the scan screen, instantiate the code firstCameraManagerandPreviewCallbackClass.
  • insurfaceIn the callback method, initialize the camera to set the camera’s configuration parameters.
  • Create a newDecodeThreadThread and start. Bind one for this threadDecodeHandler.
  • Gets the camera frame data converterbyteThe array back toDecodeHandlerTo decode.

The above has completed the camera image to decode the source analysis, the previous analysis can know that the decoding method is executed in the child thread, so the child thread decoding success, how to inform the main thread can, in fact, very simple, can know the answer from the decode handler decode method, The code for the decode method is as follows

private void decode(byte[] data, int width, int height) {
    long start = System.nanoTime();
    Result rawResult = null;
    PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
    if(source ! =null) {
      BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
      try {
      // Get the result of decoding
        rawResult = multiFormatReader.decodeWithState(bitmap);
      } catch (ReaderException re) {
        // continue
      } finally{ multiFormatReader.reset(); }}// Get the handler in CaptureActivity
    Handler handler = activity.getHandler();
    if(rawResult ! =null) {
      // Don't log the barcode contents for security.
      long end = System.nanoTime();
      Log.d(TAG, "Found barcode in " + TimeUnit.NANOSECONDS.toMillis(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);
        // Send message to the handler in CaptureActivitymessage.sendToTarget(); }}else {
      if(handler ! =null) { Message message = Message.obtain(handler, R.id.decode_failed); message.sendToTarget(); }}}Copy the code

As you can see from the above code, sending decoded results to the main thread takes advantage of Android’s Handler mechanism.

conclusion

Because the goal of this paper is to master the steps of decoding, some detailed codes are not analyzed, such as the configuration of camera parameters, whether the image after scanning is portrait or landscape, how to obtain the best image data for analysis, etc. The details will be explained in the following article, the following article will also analyze how to obtain the two-dimensional code on the image and decode.

Click here for the source code

This article has been published by the public id AndroidShared

Scan the code to pay attention to the public number, reply “get information” have surprise