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 first
CameraManager
andPreviewCallback
Class. - in
surface
In the callback method, initialize the camera to set the camera’s configuration parameters. - Create a new
DecodeThread
Thread and start. Bind one for this threadDecodeHandler
. - Gets the camera frame data converter
byte
The array back toDecodeHandler
To 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