目录
  1. 1. 精简代码
  2. 2. 源码分析
    1. 2.1. onCreate源码分析
    2. 2.2. onResume源码分析
  3. 3. 结束语
ZXing源码解析二:掌握解码步骤

前言:上篇文章已经让源码运行起来了,但是还存在很多与扫描二维码无关的代码,本篇将删除无用的代码只保留与扫码有关的代码,同时分析解码的步骤。

精简代码

  本篇文章的目标是分析出解码的步骤,为了不被无关的代码干扰,将会对源码进行精简,只保留与解码有关的代码。

  主要删减的代码就是识别出二维码的内容后,一些其他的操作,如分享,记录扫描的历史,搜索解析结果等。删除之后的android模块的结构如下

Android模块结构
当然,这不是最终删减的版本,可能在分析源码的时候,发现无用的代码,会继续删除,最终的代码,我会在文末给出Github链接。

源码分析

  为了方便理解及记忆ZXing解码的步骤,我会边分析边画UML的序列图,最后,分析完解码的步骤,会有一个完整的序列图。现在,从主程序的入口开始分析,就是CaptureActivityonCreate方法。

onCreate源码分析

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
//扫码的时候屏幕长亮
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.capture);

hasSurface = false;
//控制activity在一段时间无操作自动finish
inactivityTimer = new InactivityTimer(this);
//管理扫码后是否有声音和震动
beepManager = new BeepManager(this);
//用来根据环境的明暗,自动开启关闭闪光灯
ambientLightManager = new AmbientLightManager(this);
//加载一些默认的配置
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
}

这个方法,主要是用来实例化一些对象和获取配置信息,上面的代码中已经有注释,就不再细说。

onResume源码分析

下面继续看Activity生命周期的第二个方法,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protected void onResume() {
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;
//省略不重要代码
//.....
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
}
}

上面代码中的一些语句标记了序号,现在来看序号“1”处的代码都做了什么,进入CameraManager类的构造方法中,代码如下

1
2
3
4
5
public CameraManager(Context context) {
this.context = context;
this.configManager = new CameraConfigurationManager(context);//1.1
previewCallback = new PreviewCallback(configManager);//1.2
}

继续跟进代码,看下“1.1”处的代码,CameraConfigurationManager构造方法中做了什么,代码如下

1
2
3
CameraConfigurationManager(Context context) {
this.context = context;
}

上面的代码就是注入了context。现在看“1.2”处的代码,PreviewCallback构造方法中做了什么,代码如下

1
2
3
PreviewCallback(CameraConfigurationManager configManager) {
this.configManager = configManager;
}

上面的这段代码可以看出,在PreviewCallback构造方法中,将CameraConfigurationManager类的实例,注入到了PreviewCallback类中。跟完了“1”处的代码,继续往下看onResume方法中的代码,这里介绍一下“2”处的代码,SurfaceHolder的作用,介绍如下

SurfaceHolder是一个接口,其作用就像一个Surface的监听器。提供访问和控制SurfaceView背后的Surface 相关的方法 (providingaccess and control over this SurfaceView’s underlying surface),它通过三个回调方法,让我们可以感知到Surface的创建、销毁或者改变。

继续往下看代码,因为在onCreate方法中,hasSurface值为false,所以,会进入else语句,也就是“3”处的代码,这句代码的作用就是绑定Surface的监听器,就是在当前的Activity中绑定Surface生命周期的回调方法。
SurfaceHolder.Callback 中定义了三个接口方法:

  • public void surfaceChanged(SurfaceHolder holder, int format, int width, int height);
    当surface发生任何结构性的变化时(格式或者大小),该方法就会被立即调用。

  • public void surfaceCreated(SurfaceHolder holder);
    当surface对象创建后,该方法就会被立即调用。

  • public void surfaceDestroyed(SurfaceHolder holder);
    当surface对象在将要销毁前,该方法会被立即调用。

知道了这三个方法在什么时候会调用,所以,这里绑定回调之后,会首先调用surfaceCreated这个回调方法,看下这个方法中的代码,如下

1
2
3
4
5
6
7
8
9
public void surfaceCreated(SurfaceHolder holder) {
if (holder == null) {
Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!");
}
if (!hasSurface) {
hasSurface = true;
initCamera(holder);
}
}

继续跟进代码,看下initCamera(holder);方法都做了什么,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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);
// 实例化一个handler并开始预览.
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();
}
}

“3.1”处的代码实例化了一个CaptureActivityHandler,看下CaptureActivityHandler的构造方法,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CaptureActivityHandler(CaptureActivity activity,
Collection<BarcodeFormat> decodeFormats,
Map<DecodeHintType,?> baseHints,
String characterSet,
CameraManager cameraManager) {
this.activity = activity;//注入activity
//新建一个线程并启动
//3.1.1
decodeThread = new DecodeThread(activity, decodeFormats, baseHints, characterSet,
new ViewfinderResultPointCallback(activity.getViewfinderView()));
decodeThread.start();
state = State.SUCCESS;

// 注入cameraManager
this.cameraManager = cameraManager;
//要求相机硬件开始将预览帧绘制到屏幕上
cameraManager.startPreview();
//开始预览,并且解码
//3.1.2
restartPreviewAndDecode();
}

现在来看“3.1.1”处新建线程都做了什么,DecodeThread构造方法的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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);
}

上面的代码可以发现,在线程的构造方法中主要是设置解码的格式。

1
如果想提升扫码速度,这里是一个可以优化的点,可以不用设置这么多格式,只设置与自己业务有关的解码格式。

都知道线程运行,会调用run方法,看下run方法中的代码,如下

1
2
3
4
5
6
public void run() {
Looper.prepare();
handler = new DecodeHandler(activity, hints);
handlerInitLatch.countDown();
Looper.loop();
}

这段代码的作用是在子线程中实例化了一个Handler与当前线程绑定。继续跟进代码,看下DecodeHandler的构造方法都做了什么,代码如下

1
2
3
4
5
DecodeHandler(CaptureActivity activity, Map<DecodeHintType,Object> hints) {
multiFormatReader = new MultiFormatReader();
multiFormatReader.setHints(hints);
this.activity = activity;
}

这段代码的作用就是将线程构造方法中设置的hints设置给实例化的MultiFormatReader,同时注入CaptureActivity的实例。

1
2
3
4
5
6
7
8
9
10
11
分析到这里,可以画出如下的序列图
![第一部分序列图](../../images/zxing2/picture2.png)
接着来分析“3.1.2”处的代码,调用的方法代码如下
```java
private void restartPreviewAndDecode() {
if (state == State.SUCCESS) {
state = State.PREVIEW;
cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
activity.drawViewfinder();
}
}

重点看下cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);这句代码,看下CameraManager中的requestPreviewFrame方法做了什么,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 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);
}
}

看下这个方法的介绍,意思是解析一个预览帧,解析的数据是一个字节数组,放进了message.obj中,宽和高放到了message.arg1message.arg2中,然后将message返回给传进来的handler,由前文可只,这个handlerDecodeHandler的实例。
好了,跟到这个方法,就不继续网下跟了,这里可以猜测一下,一帧图像解析后会回调PreviewCallback类中的onPreviewFrame方法,这个方法的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
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");
}
}

不难看出,这里是把解析后的数据发送给了DecodeHandler,最终会调用DecodeHandler类中的handleMessage方法,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}

而上面代码中的message.what的值刚好是R.id.decode,自然就进入了decode方法。
分析到这里,在来看下现在的时序图,如下

根据这个序列图和前文,可以知道以下内容

  • 进入扫码界面会先实例化CameraManagerPreviewCallback类。
  • surface回调方法中,初始化相机设置相机的配置参数。
  • 新建一个DecodeThread线程并启动。为此线程绑定一个DecodeHandler
  • 获取相机帧数据转换程byte数组传回DecodeHandler进行解码。

  上文已经完成相机获取图像到进行解码的源码分析,从前面的分析可以知道,解码的方法是在子线程中执行的,那么子线程解码成功,怎么通知主线程能,其实非常简单,可以从DecodeHandler中的decode方法中知道答案,decode方法的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 {
//获取解码的结果
rawResult = multiFormatReader.decodeWithState(bitmap);
} catch (ReaderException re) {
// continue
} finally {
multiFormatReader.reset();
}
}
//获取了CaptureActivity中的handler
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);
//将message发送给CaptureActivity中的handler
message.sendToTarget();
}
} else {
if (handler != null) {
Message message = Message.obtain(handler, R.id.decode_failed);
message.sendToTarget();
}
}
}

从上面的代码中可以发现将解码的结果发送给主线程是利用Android的Handler机制。

结束语

  因为本文的目标是掌握解码的步骤,所以一些细节性的代码并没有进行分析,如配置相机的参数,扫码后的图像是竖屏还是横屏,怎样获取最佳的图像数据进行解析等。细节性的东西将会放到后面的文章进行讲解,后面的文章还会分析具体是怎么获取图像上的二维码并进行解码的。

  点击这里获取源码

本文已由公众号“AndroidShared”首发

欢迎关注我的公众号

文章作者: wizardev
文章链接: http://pi.wizardev.com:88/blog/ZXing%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%BA%8C%EF%BC%9A%E6%8E%8C%E6%8F%A1%E8%A7%A3%E7%A0%81%E6%AD%A5%E9%AA%A4/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 wizardev的博客

评论