一、需求说明
由于工作需要,已开发一款支持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上看开源代码介绍,有如下说明:
ZXing能满足要求,必须加上。
三、软件初步设计
既然是扫码软件,必不可少的要加上一个camera模块。一开始我是想用去年挺火的RxJava来实现的,所以找了一下github上的开源库,发现一款github.com/ragnraok/RxCamera库,拿来尝试了一下,功能是能实现了,但是在旋转屏幕的时候会挂掉,只能限制屏幕的旋转,加上我是用RxJava来实现的,而最新的已经是用了RxJava2了(尝试改了一下RxCamera支持RxJava2,确实也能用),加上环境原因,所以最终决定自己写一个简单的RxExecutor(说是Rx,其实并没有链式调用)来实现单线程的camera控制。
其次,由于要同时支持ZXing和ZBar库,所以对这两个库要进行抽象,定为barcode模块。
至此,可以得到5个模块的主要依赖关系如下:
四、开发环境
当然是优先在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,经查由于文字的颜色和默认背景色都为白色,所以没显示出来。
随笔完毕。