Android APP: ZXing和ZBar库扫码软件开发

一、需求说明

由于工作需要,已开发一款支持QR Code、Data Matrix和PDF 417的Android扫码软件,在此写几篇随笔。本人原来是做嵌入式C开发的,基本没有面向对象开发的工作经验,因此如果有写的有什么不当之处,欢迎大家指正,小弟在此谢过!

二、需求分析

扫码软件的网络开源库通常用两个:ZXing和ZBar。ZXing是用纯Java开发的库,而ZBar则基于C语言开发。从我个人经验来看,用C开发的库在效率上肯定是高于Java的,所以先找了找关于ZBar的相关说明。

在github.com/zbar/zbar上看开源代码的介绍,有如下说明:

It supports EAN-13/UPC-A, UPC-E, EAN-8, Code 128, Code 39, Codabar, Interleaved 2 of 5 and QR Code.

从需求角度来说,ZBar不支持Data Matrix和PDF 417,所以可以否决这个方案。但是有点强迫症的我,想到万一以后需求变化了,或者该库能支持上这两种编码了,到时候再考虑添加进来的话,势必要做很多修改,所以决定再代码上暂时支持,可以不使用。

那么,ZXing库呢?github.com/zxing/zxing上看开源代码介绍,有如下说明:


Android APP: ZXing和ZBar库扫码软件开发_第1张图片


ZXing能满足要求,必须加上。

三、软件初步设计

既然是扫码软件,必不可少的要加上一个camera模块。一开始我是想用去年挺火的RxJava来实现的,所以找了一下github上的开源库,发现一款github.com/ragnraok/RxCamera库,拿来尝试了一下,功能是能实现了,但是在旋转屏幕的时候会挂掉,只能限制屏幕的旋转,加上我是用RxJava来实现的,而最新的已经是用了RxJava2了(尝试改了一下RxCamera支持RxJava2,确实也能用),加上环境原因,所以最终决定自己写一个简单的RxExecutor(说是Rx,其实并没有链式调用)来实现单线程的camera控制。

其次,由于要同时支持ZXing和ZBar库,所以对这两个库要进行抽象,定为barcode模块。

至此,可以得到5个模块的主要依赖关系如下:


Android APP: ZXing和ZBar库扫码软件开发_第2张图片

四、开发环境

当然是优先在JDK1.8、Android Studio下开发咯,但是***原因,需要支持另外一个环境,而RaJava2在两种环境中使用会有不同,所以弃用。


五、详细设计

5.1 RxExecutor模块

名字上看,我用了线程池来实现。在本程序中,会出现2种使用场景:单线程的Camera和用来解码的多线程,因此RxExecutor为抽象类。

任务在子线程中执行完毕后调用回调函数,为了减少调用者的工作,提供了内部Handler来实现,保证回调函数在UI线程中运行。

抽象的两个方法如下:

protected abstract ExecutorService createExecutor();

protected abstract Handler createHandler();

提供接口定义如下:

// 单参数返回任务

public interface Func1 {

    T call() throws Exception;

}

// 类型转换任务

public interface Func2 {

    V call(K k) throws Exception;

}

// 观察者角色

public interface Observer {

    void onSuccess(T t);

    void onError(Exception e);

}

// 消费者角色

public interface Consumer {

    void accept(T t);

}

提供外部调用方法如下:

// 执行单参数返回任务

public void operate(final Func1 func, final Observer observer);

public  void operate(final Func1 func, final Consumer success);

public  void operateDelay(final Func1 func, final Observer observer, long delay);

// 执行类型转换任务

public  void map(final K k, final Func2 func, final Observer observer);

public  void map(final K k, final Func2 func, final Consumer success);

public  void mapDelay(final K k, final Func2 func, final Observer observer, long delay);

5.2 Camera模块

提供参数配置、摄像头任务执行的功能。

5.2.1 CameraConfig

提供参数配置功能。方便起见,提供链式调用,我们可以采用Builder设计模式来实现。

这里初步定下摄像头必配参数:ID、预览分辨率、旋转角度和预览控件。

5.2.2 CameraInstance

提供外部操作摄像头的接口和方法。

接口为获取Preview Frame时的回调:

public interface OneShotCallback {

    void onPreview(int format, int orientation, byte[] data, int width, int height);

}

操作方法包括:

public void openAndStartPreview(final CameraConfig config, final Observer callback);

public void close(final Observer callback);

publicvoid focus(final Observercallback, int delay);

public void getOneShot(final OneShotCallback callback);

public void torch(final boolean isOn, final Observercallback);

public boolean isTorchOn();

publicvoidre setPreviewSize(final Point size, final Observercallback);  // 重设分辨率

5.2.3 其他(略)

5.3 barcode模块

本模块除了对两个库抽象之外,还提供了基本的UI显示功能。

5.3.1 decode子模块

该子模块的作用是对两个库抽象,主要包括结果和解码器抽象。

结果抽象BarcodeResult,目前只做了简单的扫码字符获取。

解码器抽象,两种类型的解码器:Bitmap和YUVImage。

public abstract class Decoder {

    public void decode(RxExecutor.Consumer consumer) {...}  // 外部调用接口

    protected abstract BarcodeResult realDecode();  // 具体库的解码子类实现

}

5.3.2 UI子模块

无它,实现了一个View和一个Activity。

View为ScanBoxView,参考了开源代码github.com/bingoogolapple/BGAQRCode-Android的实现,包括遮罩层、矩形框和提示文字三部分。

Activity采用MVP结构,定义View接口如下:

interface View {

    Contextget Context();

    int getOrientation();  // 获得当前屏幕的实时旋转角度

    void show Progress();

    void hideProgress();

    void showResult(String result);

    void showError(Exception e);

    void showPicNotBarcode();

    void torchBtn(booleanisPressed);  // 手电筒按钮的UI控制

    void createResolutionDialog(List resolutions,Point curResolution);  // 创建预览分辨率对话框(并不显示)

}

定义的Presenter接口如下:

interface Presenter {

    void openCamera(SurfaceView surface);  // 在SurfaceView上打开摄像头

    void closeCamera();  // 关闭摄像头

    void changeResolution(Point resolution);  // 实时更改摄像头分辨率

    void switchTorch();  // 切换手电筒开关状态

    void decodeUriImage(Uri uri);  // 根据图片Uri解码,可以是本地图片,也可以是网络图片,基于Google的开源Glide库实现。

}

5.4 ZXing模块

UI和摄像头控制都已经在Barcode中实现了,因此本模块的任务很简单,实现两个解码器,再具化UI。

Bitmap解码器:Bitmap->LuminanceSource->BinaryBitmap后,使用MultiFormatReader来解码,得到Result,转化为BarcodeResult。

YUVImage解码器:YUVImage->角度旋转->LuminanceSource->BinaryBitmap后,使用MultiFormatReader来解码,得到Result,转化为BarcodeResult。

5.5 ZBar模块

同ZXing,实现两个解码器,都将图片数据转化为Image类型,然后调用ImageScanner来解码,得到SymbolSet结果,转化为BarcodeResult。

六、主要碰到问题记录

6.1 SurfaceHolder

SurfaceHolder的callback设置如果在SurfaceView已经初始化完成,那么会收不到surfaceCreated消息,不掉用surfaceChanged。由于我是在Activity的onResume和onPause中进行开关摄像头操作,所以在Camera模块中必须于UI线程中先调用addCallback,再在子线程中open camera。

6.2 RxJava2

在JDK 1.8上编译通过的代码,到了JDK1.7上编译不过,查了下RxJava2的资料,发现两个版本上的用法有些许区别,需要修改。

6.3 预览分辨率

Camera的预览分辨率是可以支持实时修改的,不必每次都重新open摄像头。

6.4 Orientation

使用OrientationEventListener可以实时监控当前屏幕的旋转角度,可以用来实现锁定屏幕旋转时,横屏也能进行条码识别(主要指一维条码)。

6.5 AlertDialog

发现AlertDialog在某些机型上显示不出来Message,经查由于文字的颜色和默认背景色都为白色,所以没显示出来。


随笔完毕。

你可能感兴趣的:(Android APP: ZXing和ZBar库扫码软件开发)