相对于官方Demo,有以下特点:
可以直接clone其仓库中的代码到本地,也可以直接下载https://github.com/zxing/zxing/releases页面的最新的打包源码。
以3.3.1版本为例,工程构建使用AndroidStudio
该源码中实际工程中需要用的是目录.\android及.\android-core及目录core下的代码。
其中android下的即是一个.\android的实例工程,该目录下的代码主要是提供一个扫码界面,并捕捉实时图片数据供解码使用,具体解码处理在.\core下的代码完成
.\android-core下只有一个文件:CameraConfigurationUtils.java,该文件作用是相机的相关属性设置类。我们可以直接copy到.\android下的合适位置
.\core下是ZXing解码的核心处理代码,我们可以直接编译该文件夹下的代码为一个jar,进行依赖,也可以直接在项目build.gradle中添加依赖: compile ‘com.google.zxing:core:3.3.1’
本例用的是AndroidStudio,过程如下:
在该Module下的build.gradle 中添加依赖:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.zxing:core:3.3.1'
}
CaptureActivity类代码如下:
<类CaptureActivity>
protected void onResume() {
super.onResume();
....
cameraManager = new CameraManager(getApplication());
....
SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
SurfaceHolder surfaceHolder = surfaceView.getHolder();
if (hasSurface) {//SurfaceView 已经被初始化并创建出来
// 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 {//SurfaceView还没有被初始化完毕,需要在其回调中打开相机
// Install the callback and wait for surfaceCreated() to init the camera.
surfaceHolder.addCallback(this);
}
}
CameraManager类的构造函数如下
public CameraManager(Context context) {
this.context = context;
this.configManager = new CameraConfigurationManager(context);//相机的参数设置管理类
previewCallback = new PreviewCallback(configManager);//相机的预览回掉系统级
}
initCamera(SurfaceHolder)
initCamera(SurfaceHodler surfaceHolder){
cameraManager.openDriver(surfaceHolder);//此处打开相机,并设置相机的相关属性,然后把相机预览图像通过SurfaceHodler显示在SurfaceView,(cameraObject.setPreviewDisplay(holder))
// Creating the handler starts the preview, which can also throw a RuntimeException.
if (handler == null) {
handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);//新建处理事件调度中心,在初始化时会创建一个,子线程,并在该线程中解析条码,解析完毕后,在回传到该CaptureActivityHandler中处理
}
}
构造函数如下:
CaptureActivityHandler(CaptureActivity activity,
Collection decodeFormats,
Map baseHints,
String characterSet,
CameraManager cameraManager) {
this.activity = activity;
decodeThread = new DecodeThread(activity, decodeFormats, baseHints, characterSet,
new ViewfinderResultPointCallback(activity.getViewfinderView()));
decodeThread.start();
state = State.SUCCESS;
// Start ourselves capturing previews and decoding.
this.cameraManager = cameraManager;
cameraManager.startPreview();
restartPreviewAndDecode();
}
其run函数如下
@Override
public void run() {
Looper.prepare();
handler = new DecodeHandler(activity, hints);//在该子线程中新建一个消息队列,以接收数据并解析条形码的信息
handlerInitLatch.countDown();
Looper.loop();
}
handleMessage()代码如下,该信息的发送正是在CameraManager初始化时初始化的相机的PreviewCallBack中调用的
@Override
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;
}
}
在相机预览到图片之后会调用previewHandler处理消息,该previewHandler的设置是通过CameraManager的requestPreviewFrame(Handler handler, int message)设置,而该方法的调用是在CaptureActivityHandler中调用的其中的handler传入的正是decodeThread.getHandler()即DecodeHandler
final class PreviewCallback implements Camera.PreviewCallback {
private static final String TAG = PreviewCallback.class.getSimpleName();
private final CameraConfigurationManager configManager;
private Handler previewHandler;
private int previewMessage;
PreviewCallback(CameraConfigurationManager configManager) {
this.configManager = configManager;
}
void setHandler(Handler previewHandler, int previewMessage) {
this.previewHandler = previewHandler;
this.previewMessage = previewMessage;
}
@Override
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");
}
}
}
DecodeThread构造函数如下
DecodeThread(CaptureActivity activity,
Collection decodeFormats,
Map 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);
Log.i("DecodeThread", "Hints: " + hints);
}
android:configChanges="keyboard|orientation"
android:screenOrientation="portrait"
同是,我们在onresum()不再根据当前手机的物理方向去修改activity的方向,而是直接设置,该方法为Activity的方法
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
Point cameraResolution = configManager.getCameraResolution();//拿取相机的分辨率
int previewHeight = cameraResolution.y;
int previewWidth = cameraResolution.x;
byte[] rotatedData = new byte[data.length];//新建转置byte数组
for (int y = 0; y < previewHeight; y++) {//转置原数组,纵向横向数据互换
for (int x = 0; x < previewWidth; x++)
rotatedData[x * previewHeight + previewHeight - y - 1] = data[x + y * previewWidth];
}
int tmp = previewWidth;
previewWidth = previewHeight;
previewHeight = tmp;//更改记录数据的宽高
data = rotatedData;
....
//发送数据到解码子线程中进行解码
}
另外,同时需要修改CameraManaget中的有效数据区域的生成方法,
public synchronized Rect getFramingRectInPreview() {
if (framingRectInPreview == null) {
Rect framingRect = getFramingRect();
if (framingRect == null) {
return null;
}
Rect rect = new Rect(framingRect);
Point cameraResolution = configManager.getCameraResolution();
Point screenResolution = configManager.getScreenResolution();
if (cameraResolution == null || screenResolution == null) {
// Called early, before wakeup even finished
return null;
}
/* rect.left = rect.left * cameraResolution.x / screenResolution.x;
rect.right = rect.right * cameraResolution.x / screenResolution.x;
rect.top = rect.top * cameraResolution.y / screenResolution.y;
rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;*/
//FIX 竖屏一维码扫不出,在转置相机PreviewCallback中的数据后,该有效裁剪区域不不修改会导致,裁剪时裁剪范围超出数据范围,此时相机preview图像为纵向,cameraResolution.y相当于图像的宽,cameraResolution.x相当于图像的高
rect.left = rect.left * cameraResolution.y / screenResolution.x;
rect.right = rect.right * cameraResolution.y / screenResolution.x;
rect.top = rect.top * cameraResolution.x / screenResolution.y;
rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;
framingRectInPreview = rect;
}
Log.d(TAG, "getFramingRectInPreview: framingRectInPreview" + framingRectInPreview.toString()+"--framingRect="+framingRect.toString());
return framingRectInPreview;
}
//从有效的绝对路径地址中生成构建BinaryBitmap所需要的图片像素数据 int[]
public static PicDataInfo getPicInfo(String picPath) {
if(TextUtils.isEmpty(picPath)){
return null;
}
PicDataInfo picDataInfo = new PicDataInfo();//自定义的类用来保存生成的数据
try {
File picFile = new File(picPath);
if (!picFile.exists()) {
return null;
}
FileInputStream fis = new FileInputStream(picFile);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 10];
int len = 0;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
baos.flush();
picDataInfo.data = baos.toByteArray();
baos.close();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 先获取原大小
Bitmap bitmap = BitmapFactory.decodeByteArray(picDataInfo.data, 0, picDataInfo.data.length);
picDataInfo.height = bitmap.getHeight();
picDataInfo.width = bitmap.getWidth();
int[] pixData = new int[picDataInfo.width * picDataInfo.height];
bitmap.getPixels(pixData, 0, picDataInfo.width, 0, 0, picDataInfo.width, picDataInfo.height);//生成pixData
picDataInfo.pixData = pixData;
} catch (Exception e) {
e.printStackTrace();
} finally {
return picDataInfo;
}
}
//解码相册中的图片数据
public String decodeCode(int[] pixels, Rect validArea) {
Result rawResult = null;
RGBLuminanceSource source = new RGBLuminanceSource(validArea.width(), validArea.height(), pixels);
if (source != null) {
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
rawResult = multiFormatReader.decodeWithState(bitmap);
Log.d(TAG, "decode result:" + rawResult.toString());
} catch (ReaderException re) {
// continue
} finally {
multiFormatReader.reset();
}
}
if (rawResult != null) {
return rawResult.getText();
}
return null;
}
扫描动画效果的实现可以参考该链接:http://blog.csdn.net/M075097/article/details/78533141
该工程特点如下: