前言
Zxing二维码是谷歌官方提供的扫码方法。
百度查的很多第三方库都是很老的Zxing代码生成的扫码库。虽然都能用,但是也有很多扫码很慢或者方法过老的情况。没有更新新的版本!
本篇就是探究一下扫码库和Zxing库的使用
Zxing的使用
首先说下,其实Zxing的二维码编码和解码的代码都写在了github项目的core
文件夹内,其实我们只需要core内的代码就可以实现二维码的编码和解码了。
谷歌官方Zxing github地址
那么我们可以将Zxing的github下的core代码全部下载下来然后放到使用的项目中,或者在项目的build.gradle中加入
dependencies {
compile 'com.google.zxing:core:3.3.2'
}
二维码的介绍
二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的;在代码编制上巧妙地利用构成计算机内部逻辑基础的“0”、“1”比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识读以实现信息自动处理:它具有条码技术的一些共性:每种码制有其特定的字符集;每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息自动识别功能、及处理图形旋转变化点。
我们可以认为二维码就是一串二进制数组保存的数据,然后再生成以正方形绘制图形。
Zxing生成二维码参数说明
Zxing将生成图形编码的方式抽象成了一个类com.google.zxing.Writer
, 在实现类中不仅仅生成二维码,还可以生成条形码等其他图形编码。
在Writer我们可以看到2个方法
BitMatrix encode(String contents, BarcodeFormat format,
int width, int height)
throws WriterException;
BitMatrix encode(String contents, BarcodeFormat format,
int width, int height,
Map<EncodeHintType,?> hints)
throws WriterException;
参数说明
参数 | 说明 |
---|---|
String contents | 编码内容 |
BarcodeFormat format | 编码方式(如:二维码、条形码…) |
int width | 宽度 |
int height | 高度 |
Map hints | 编码参数设置 |
我们在看看编码参数设置有哪些
参数 | 说明 |
---|---|
ERROR_CORRECTION | 容错率,指定容错等级,对于QRCode类型来源com.google.zxing.qrcode.decoder.ErrorCorrectionLevel ,对于Aztec类型为Integer 等… |
CHARACTER_SET | 编码集,类型为String ,可写”utf-8”等… |
DATA_MATRIX_SHAPE | 指定生成的数据矩阵的形状,类型为com.google.zxing.datamatrix.encoder.SymbolShapeHint |
MIN_SIZE | 指定最小的条码大小(已废弃),新方法设置在com.google.zxing.datamatrix.DataMatrixWriter#encode(String, BarcodeFormat, int, int) 中 |
MAX_SIZE | 指定最大的条码大小(已废弃),同上 |
MARGIN | 生成条码的时候使用,指定边距,单位像素,受格式的影响,类型Integer 或String 代表的数字类型 |
PDF417_COMPACT | 指定是否使用PDF417紧凑模式,类型为boolean |
PDF417_COMPACTION | 指定PDF417的紧凑类型,类型为com.google.zxing.pdf417.encoder.Compaction 或String |
PDF417_DIMENSIONS | 指定PDF417的最大最小行列数,类型为com.google.zxing.pdf417.encoder.Dimensions |
AZTEC_LAYERS | aztec编码相关,指定aztec所需的层数,类型Integer 或String 代表的数字类型 |
QR_VERSION | 指定二维码版本,类型Integer 或String 代表的数字类型 |
GS1_FORMAT | 指定数据编码应以GS1标准,类型为boolean |
具体的详细说明在打开com.google.zxing.EncodeHintType
的代码上面有详细的说明。
那么我们需要设置的二维码,正常只会用到ERROR_CORRECTION
、CHARACTER_SET
、MARGIN
、QR_VERSION
这几个参数。编码集默认是”ISO-8859-1”需要设置成”utf-8”,后两个参数一般是使用默认
我们看下ERROR_CORRECTION
容错等级分别是
参数 | 说明 |
---|---|
L | 7%的矫正率 |
M | 15%的矫正率 |
Q | 25%的矫正率 |
H | 30%的矫正率 |
这个ERROR_CORRECTION
设置就可以让我们的生成的二维码识别率提高。
Zxing生成二维码
接下来,我们试着使用Zxing代码生成二维码的使用
我们使用的是继承于Writer
的com.google.zxing.qrcode.QRCodeWriter
方法中的encode
来创建二维码
public static BitMatrix encode(String QRcode)
throws WriterException {
Map<EncodeHintType,Object> hints = new HashMap();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
return new QRCodeWriter().encode(QRcode, BarcodeFormat.QR_CODE,300,300,hints);
}
这边方法回调给我们的却是一个com.google.zxing.common.BitMatrix
类型的值。这边我们就需要将这个类型转为Bitmap,这样我们就能使用了。
BitMatrix转Bitmap
public static Bitmap BitMatrix2Bitmap(BitMatrix bitMatrix){
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
int[] pixels = new int[width * height];
//将BitMatrix的像素保存下来
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
pixels[y * width + x] = bitMatrix.get(x,y) ? 0xFF000000 : 0xFFFFFFFF;
}
}
//创建一样大小的Bitmap
Bitmap bitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
//将BitMatrix的像素绘制到Bitmap
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
这样我们就实现了最简单的二维码生成了。
Zxing解析二维码参数说明
Zxing将解析图形编码的方式抽象成了一个类com.google.zxing.Reader
, 和上面生成二维码一样这边不仅仅能解析二维码,还可以解析其他图形编码。
我们依旧开始先说里面的方法
Result decode(BinaryBitmap image)
throws NotFoundException, ChecksumException, FormatException;
Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
throws NotFoundException, ChecksumException, FormatException;
/**
* Resets any internal state the implementation has after a decode, to prepare it
* for reuse.
*/
void reset();
首先来说下reset()这个方法,在上面我将源代码的说明也写上了。翻译为重置解码后实现的任何内部状态,准备重用。
另外解析代码的参数说明如下
参数 | 说明 |
---|---|
BinaryBitmap image | 解码的图像 |
Map hints | 解析的参数 |
我们在看看解析编码参数设置有哪些
参数 | 说明 |
---|---|
OTHER(Object.class) | 未指定作用,应用自定义,Object类型 |
PURE_BARCODE(Void.class) | 图像是一个纯粹的黑白图像的条形码,Boolean类型 |
POSSIBLE_FORMATS(List.class) | 图像是哪几种编码格式,List或BarcodeFormat类型 |
TRY_HARDER(Void.class) | 优化识别率,为了准确性花费更长的时间,识别速度会变慢,Boolean类型 |
CHARACTER_SET(String.class) | 编码集设置,String类型 |
ALLOWED_LENGTHS(int[].class) | 设置编码数据长度,不解析多余的数据,int[]类型 |
ASSUME_CODE_39_CHECK_DIGIT(Void.class) | CODE_39的使用,Boolean类型 |
ASSUME_GS1(Void.class) | GS1条码处理,Boolean类型 |
RETURN_CODABAR_START_END(Void.class) | CODABAR编码使用,Boolean类型 |
NEED_RESULT_POINT_CALLBACK(ResultPointCallback.class) | 当解析到可能的结束点时进行回调,Boolean类型 |
ALLOWED_EAN_EXTENSIONS(int[].class) | 允许EAN或UPC编码有额外的长度,int[]类型 |
这边我们只设置二维码的解析,常用的方法只有PURE_BARCODE
、POSSIBLE_FORMATS
、TRY_HARDER
、CHARACTER_SET
,其中PURE_BARCODE
就默认设置就好了,其他设置TRY_HARDER
为Boolean.TRUE就是加大识别率,但是速度会变慢这边按需求设置就好了,CHARACTER_SET
为”utf-8”,POSSIBLE_FORMATS
为了速度可以只设置一个就是BarcodeFormat.QR_CODE就可以了。
Zxing解析二维码
接下来,我们试着使用Zxing代码解析二维码的使用
我们使用的是继承于Reader
的com.google.zxing.qrcode.QRCodeReader
方法中的decode
来解析二维码
public static String decode(BinaryBitmap image)
throws FormatException, ChecksumException, NotFoundException {
Map<DecodeHintType, Object> hints = new HashMap<>();
hints.put(DecodeHintType.CHARACTER_SET, "utf-8");
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE);
Result result = new QRCodeReader().decode(image,hints);
return result.getText();
}
这边我们可以看到,我们正常需要传入一个Bitmap
而不是com.google.zxing.BinaryBitmap
,那么我们就需要将Bitmap
转为BinaryBitmap
。
Bitmap转BinaryBitmap
public static BinaryBitmap Bitmap2BinaryBitmap(Bitmap bitmap){
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int[] pixels = new int[width * height];
//获取像素
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
RGBLuminanceSource source = new RGBLuminanceSource(width,height,pixels);
return new BinaryBitmap(new HybridBinarizer(source));
}
结合上面的方法,我们就实现了二维码的解析了。
相机结合Zxing实现扫码
我们可以看到,如果单独使用上面的方法就是可以实现的简单的生成二维码和解析二维码。但是我们正常使用的使用是使用相机然后实现扫码识别的。这边我们就需要引入相机,下面我将写一个简单的相机,然后通过拍照获取的Bitmap然后解析二维码。
创建一个普通的相机
我这边就直接贴相机的代码了,想要具体了解的可以百度或者看这篇文章Android自定义Camera最佳入门实例,当然这是以前的Camera,5.0之后的Camera2这边没有涉及。
效果如下
全部代码如下
activity_qr_code.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/fl_qr"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
<Button
android:id="@+id/btn_qr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
android:text="拍照" />
<ImageView
android:id="@+id/iv_qr"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_margin="20dp" />
</RelativeLayout>
CameraPreview.java
import android.content.Context;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
/**
* CameraPreview
* Author: gjn.
* Time: 2018/3/16.
*/
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private Camera camera;
private SurfaceHolder surfaceHolder;
public CameraPreview(Context context, Camera camera) {
super(context);
this.camera = camera;
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
camera.setPreviewDisplay(holder);
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (surfaceHolder.getSurface() == null) {
return;
}
camera.stopPreview();
try {
camera.setPreviewDisplay(surfaceHolder);
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
}
QrCodeActivity.java
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.hardware.Camera;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Toast;
import com.google.zxing.ChecksumException;
import com.google.zxing.FormatException;
import com.google.zxing.NotFoundException;
public class QrCodeActivity extends AppCompatActivity {
private Camera mCamera;
private CameraPreview mPreview;
private FrameLayout fl;
private ImageView iv;
private Button btn;
private Camera.PictureCallback mCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
//竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
//全屏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_qr_code);
fl = (FrameLayout) findViewById(R.id.fl_qr);
iv = (ImageView) findViewById(R.id.iv_qr);
btn = (Button) findViewById(R.id.btn_qr);
iv.setVisibility(View.GONE);
mCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(final byte[] data, Camera camera) {
new Thread(new Runnable() {
@Override
public void run() {
if (data != null) {
Bitmap bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
bitmap = rotateBitmapByDegree(bitmap,90);
final Bitmap endBitmap = bitmap;
runOnUiThread(new Runnable() {
@Override
public void run() {
iv.setImageBitmap(endBitmap);
iv.setVisibility(View.VISIBLE);
String s = "";
try {
s = qrUtils.readQRBitmap(endBitmap);
} catch (FormatException | ChecksumException | NotFoundException e) {
e.printStackTrace();
}
Toast.makeText(QrCodeActivity.this, "==>\n"+s,
Toast.LENGTH_SHORT).show();
}
});
}
}
}).start();
}
};
initCamera();
onClikc();
}
private void onClikc() {
fl.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
Toast.makeText(QrCodeActivity.this, "聚焦成功!", Toast.LENGTH_SHORT).show();
}
});
}
});
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
mCamera.takePicture(null,null,mCallback);
}
});
}
});
iv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
iv.setVisibility(View.GONE);
mCamera.startPreview();
}
});
}
private void initCamera() {
try {
mCamera = Camera.open();
}catch (Exception e){
e.printStackTrace();
}
mPreview = new CameraPreview(this,mCamera);
fl.addView(mPreview);
mCamera.setDisplayOrientation(90);
}
private Bitmap rotateBitmapByDegree(Bitmap bm, int i) {
Matrix matrix = new Matrix();
matrix.postRotate(i);
Bitmap bitmap = Bitmap.createBitmap(bm, 0, 0,
bm.getWidth(), bm.getHeight(),
matrix, true);
if (bitmap == null) {
bitmap = bm;
}
return bitmap;
}
private void closeCamera() {
if (mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
}
@Override
protected void onResume() {
mCamera.stopPreview();
super.onResume();
}
@Override
protected void onPause() {
mCamera.startPreview();
super.onPause();
}
@Override
protected void onDestroy() {
closeCamera();
super.onDestroy();
}
}
总结
记录二维码的学习,顺便之后如果需要用到别人的二维码库,其实是可以直接修改他们的Zxing版本,达到更新Zxing的croe的更新。