参考
zxing扫描二维码和识别图片二维码及其优化策略
一、zxing 集成
我集成 zxing 的主要方法是复制关键代码到自己的项目中。集成的代码中删除了share,history,help等无用的代码。集成后的效果图如下所示:
注:该UI是自定义的UI界面,并不是官方自带的
zxing 的官方项目地址是: https://github.com/zxing/zxing。当前最新版是3.3.0。目录结构如下:
跟android有关的 是 core,android-core,android-integration ,以及android。其中 android 包是一个完整的demo。里面包含了一些分享,历史管理,设置,帮助之类的主菜单。
进入release页面:https://github.com/zxing/zxing/releases,下载最新的代码。
在使用时可以把 目录 core 导出为 jar 包,然后放入项目的lib目录中,也可以直接添加依赖(添加依赖时去官网看看当前的最新版本):
implementation 'com.google.zxing:android-core:3.3.0'
implementation 'com.google.zxing:core:3.3.3'
把 zxing 包中的关键类拷贝到项目中
拷贝后如下图所示
拷贝相关资源文件
1. 将 drawable 中的内容拷贝过来
2. 将raw文件夹都拷过去
3. values下的文件拷贝要小心一点!比如:对于colors.xml文件,我们需要把其中Zxing相关的代码考到自己的colors.xml中去, 而不是简单的替换,其他文件也类似!
4. 将 xml 文件拷贝过去
5 拷贝manifest.xml文件中的相关权限及CaptureActivity 类加入代码
// CaptureActivity 类加入的部分代码,扫描页面全屏没有状态栏
@Override
public void onCreate(Bundle icicle) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate(icicle);
//window设置标志位,保证屏幕常亮不会黑屏
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_qrcode_capture_layout);
inactivityTimer = new InactivityTimer(this);
beepManager = new BeepManager(this);
ambientLightManager = new AmbientLightManager(this);
cameraManager = new CameraManager(getApplication());
}
window 设置标志位,保证屏幕常亮不会黑屏,
InactivityTimer 保证在电量较低的时候且一段时间没有激活的时候,关闭CaptureActivity,
BeepManager 是用来扫码时发出声音和震动的,
AmbientLightManager 是用来控制感光的,以此来控制闪光灯的开闭
CaptureActivity,扫描界面,也是官方demo的主界面。
CaptureActivityHandler,辅助扫描界面,进行一些逻辑的处理,消息的转发。
CameraManager,Camera,相机有关的部分,如 预览,自动聚焦
DecodeThread,DecodeHandler, 跟解码有关的类,线程,消息处理
BarcodeFormat, DecodeHintType, 支持的一些类型,格式,配置。如,二维码,各种条形码,字符集。
自定义扫码框 ViewfinderView
以下是关键代码
@Override
public void onDraw(Canvas canvas) {
if (cameraManager == null) {
return; // not ready yet, early draw before done configuring
}
Rect frame = cameraManager.getFramingRect();
Rect previewFrame = cameraManager.getFramingRectInPreview();
if (frame == null || previewFrame == null) {
return;
}
int width = canvas.getWidth();
int height = canvas.getHeight();
// Draw the exterior (i.e. outside the framing rect) darkened
paint.setColor(resultBitmap != null ? resultColor : maskColor);
canvas.drawRect(0, 0, width, frame.top, paint);
canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);
canvas.drawRect(0, frame.bottom + 1, width, height, paint);
if (resultBitmap != null) {
// Draw the opaque result bitmap over the scanning rectangle
paint.setAlpha(CURRENT_POINT_OPACITY);
canvas.drawBitmap(resultBitmap, null, frame, paint);
} else {
// Draw a red "laser scanner" line through the middle to show decoding is active
paint.setColor(Color.BLUE);
paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
//绘制左上角的两个小矩形
canvas.drawRect(frame.left, frame.top, frame.left + ScreenRate, frame.top + CORNER_WIDTH, paint);
canvas.drawRect(frame.left, frame.top, frame.left + CORNER_WIDTH, frame.top + ScreenRate, paint);
//绘制右上角的两个小矩形
canvas.drawRect(frame.right - ScreenRate, frame.top, frame.right, frame.top + CORNER_WIDTH, paint);
canvas.drawRect(frame.right - CORNER_WIDTH, frame.top, frame.right, frame.top + ScreenRate, paint);
//绘制左下角的两个小矩形
canvas.drawRect(frame.left, frame.bottom - CORNER_WIDTH, frame.left + ScreenRate, frame.bottom, paint);
canvas.drawRect(frame.left, frame.bottom - ScreenRate, frame.left + CORNER_WIDTH, frame.bottom, paint);
//绘制右下角的两个小矩形
canvas.drawRect(frame.right - ScreenRate, frame.bottom - CORNER_WIDTH, frame.right, frame.bottom, paint);
canvas.drawRect(frame.right - CORNER_WIDTH, frame.bottom - ScreenRate, frame.right, frame.bottom, paint);
//记录矩形框的上和下
if (!isFirst) {
isFirst = true;
slideTop = frame.top;
slideBottom = frame.bottom;
}
//定义好扫描线每秒移动速度
slideTop += SPEED_DISTANCE;
if (slideTop >= slideBottom) {
slideTop = frame.top;
}
//绘制扫描线
Rect lineRect = new Rect();
lineRect.left = frame.left;
lineRect.right = frame.right;
lineRect.top = slideTop;
lineRect.bottom = slideTop + 18;
//绘制一个bitmap
canvas.drawBitmap(((BitmapDrawable) (getResources().getDrawable(R.drawable.fle))).getBitmap(),
null, lineRect, paint);
//绘制文本
paint.setColor(Color.WHITE);
paint.setTextSize(TEXT_SIZE * density);
paint.setAlpha(0x40);
paint.setTypeface(Typeface.create("System", Typeface.BOLD));
String text = getResources().getString(R.string.scan_text);
float textWidth = paint.measureText(text);
canvas.drawText(text, (width - textWidth) / 2, frame.bottom + (float) TEXT_PADDING_TOP * density, paint);
float scaleX = frame.width() / (float) previewFrame.width();
float scaleY = frame.height() / (float) previewFrame.height();
List currentPossible = possibleResultPoints;
List currentLast = lastPossibleResultPoints;
int frameLeft = frame.left;
int frameTop = frame.top;
if (currentPossible.isEmpty()) {
lastPossibleResultPoints = null;
} else {
possibleResultPoints = new ArrayList<>(5);
lastPossibleResultPoints = currentPossible;
paint.setAlpha(CURRENT_POINT_OPACITY);
paint.setColor(resultPointColor);
synchronized (currentPossible) {
for (ResultPoint point : currentPossible) {
canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
frameTop + (int) (point.getY() * scaleY),
POINT_SIZE, paint);
}
}
}
if (currentLast != null) {
paint.setAlpha(CURRENT_POINT_OPACITY / 2);
paint.setColor(resultPointColor);
synchronized (currentLast) {
float radius = POINT_SIZE / 2.0f;
for (ResultPoint point : currentLast) {
canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
frameTop + (int) (point.getY() * scaleY),
radius, paint);
}
}
}
// Request another update at the animation interval, but only repaint the laser line,
// not the entire viewfinder mask.
postInvalidateDelayed(ANIMATION_DELAY,
frame.left - POINT_SIZE,
frame.top - POINT_SIZE,
frame.right + POINT_SIZE,
frame.bottom + POINT_SIZE);
}
}
二、zxing 识别图片二维码
主要代码是在 CaptureActivity 中完成.
首先要打开手机相册,找到二维码图片,代码如下所示:
// 打开手机中的相册
// "android.intent.action.GET_CONTENT"
Intent innerIntent = new Intent(Intent.ACTION_GET_CONTENT);
innerIntent.setType("image/*");
Intent wrapperIntent = Intent.createChooser(innerIntent, "选择二维码图片");
startActivityForResult(wrapperIntent, REQUEST_CODE);
然后在以下方法中接收处理返回结果
private Uri uri;
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
// 首先获取到此图片的Uri
uri = data.getData();
//获取选取的图片的绝对地址
String photoPath = getRealFilePath(this, data.getData());
if (photoPath == null) {
Toast.makeText(getApplicationContext(), "路径获取失败", Toast.LENGTH_SHORT).show();
} else {
//解析图片
parsePhoto(photoPath);
}
}
}
/**
* 获取选取的图片的绝对地址
* @param c
* @param uri
* @return
*/
private String getRealFilePath(Context c, Uri uri) {
String result;
Cursor cursor = c.getContentResolver().query(uri,
new String[]{MediaStore.Images.ImageColumns.DATA},
null, null, null);
if (cursor == null) {
result = uri.getPath();
} else {
cursor.moveToFirst();
int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
result = cursor.getString(index);
cursor.close();
}
return result;
}
其中的核心内容是zxing扫描路径图片的功能,是parsePhoto方法 由于这个方法是耗时的 所以需要用AsyncTask 或者Rxjava 这里用的是AsyncTask。具体代码如下:
/**
* 启动线程解析二维码图片
*
* @param path
*/
private void parsePhoto(String path) {
//启动线程完成图片扫码
new QrCodeAsyncTask(this, path).execute(path);
}
/**
* AsyncTask 静态内部类,防止内存泄漏
*/
static class QrCodeAsyncTask extends AsyncTask {
private WeakReference mWeakReference;
private String path;
public QrCodeAsyncTask(Activity activity, String path) {
mWeakReference = new WeakReference<>(activity);
this.path = path;
}
@Override
protected String doInBackground(String... strings) {
// 解析二维码/条码
return QRCodeDecoder.syncDecodeQRCode(path);
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
//识别出图片二维码/条码,内容为s
CaptureActivity activity = (CaptureActivity) mWeakReference.get();
if (activity != null) {
activity.handleQrCode(s);
}
}
}
/**
* 处理图片二维码解析的数据
* @param s
*/
public void handleQrCode(String s) {
if (null == s) {
Toast.makeText(getApplicationContext(), "图片格式有误", Toast.LENGTH_SHORT).show();
} else {
// 识别出图片二维码/条码,内容为s
handleResult(s);
//Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show();
}
}
private void handleResult(String result) {
Intent intent = new Intent();
intent.putExtra(SCAN_RESULT, result);
setResult(Activity.RESULT_OK, intent);
finish();
}
其中 QRCodeDecoder 类是自己写的,是解析二维码图片的关键类,代码如下:
/**
* @desciption: 解析二维码/条码
*/
public class QRCodeDecoder {
public static final Map HINTS = new EnumMap<>(DecodeHintType.class);
static {
List allFormats = new ArrayList<>();
allFormats.add(BarcodeFormat.AZTEC);
allFormats.add(BarcodeFormat.CODABAR);
allFormats.add(BarcodeFormat.CODE_39);
allFormats.add(BarcodeFormat.CODE_93);
allFormats.add(BarcodeFormat.CODE_128);
allFormats.add(BarcodeFormat.DATA_MATRIX);
allFormats.add(BarcodeFormat.EAN_8);
allFormats.add(BarcodeFormat.EAN_13);
allFormats.add(BarcodeFormat.ITF);
allFormats.add(BarcodeFormat.MAXICODE);
allFormats.add(BarcodeFormat.PDF_417);
allFormats.add(BarcodeFormat.QR_CODE);
allFormats.add(BarcodeFormat.RSS_14);
allFormats.add(BarcodeFormat.RSS_EXPANDED);
allFormats.add(BarcodeFormat.UPC_A);
allFormats.add(BarcodeFormat.UPC_E);
allFormats.add(BarcodeFormat.UPC_EAN_EXTENSION);
HINTS.put(DecodeHintType.TRY_HARDER, BarcodeFormat.QR_CODE);
HINTS.put(DecodeHintType.POSSIBLE_FORMATS, allFormats);
HINTS.put(DecodeHintType.CHARACTER_SET, "utf-8");
}
private QRCodeDecoder() {
}
/**
* 同步解析本地图片二维码。该方法是耗时操作,请在子线程中调用。
*
* @param picturePath 要解析的二维码图片本地路径
* @return 返回二维码图片里的内容 或 null
*/
public static String syncDecodeQRCode(String picturePath) {
return syncDecodeQRCode(getDecodeAbleBitmap(picturePath));
}
/**
* 同步解析bitmap二维码。该方法是耗时操作,请在子线程中调用。
*
* @param bitmap 要解析的二维码图片
* @return 返回二维码图片里的内容 或 null
*/
public static String syncDecodeQRCode(Bitmap bitmap) {
Result result = null;
RGBLuminanceSource source = null;
try {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
source = new RGBLuminanceSource(width, height, pixels);
result = new MultiFormatReader().decode(new BinaryBitmap(new HybridBinarizer(source)), HINTS);
return result.getText();
} catch (Exception e) {
e.printStackTrace();
if (source != null) {
try {
result = new MultiFormatReader().decode(new BinaryBitmap(new GlobalHistogramBinarizer(source)), HINTS);
return result.getText();
} catch (Throwable e2) {
e2.printStackTrace();
}
}
return null;
}
}
/**
* 将本地图片文件转换成可解码二维码的 Bitmap。为了避免图片太大,这里对图片进行了压缩。感谢 https://github.com/devilsen 提的 PR
*
* @param picturePath 本地图片文件路径
* @return
*/
private static Bitmap getDecodeAbleBitmap(String picturePath) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(picturePath, options);
int sampleSize = options.outHeight / 400;
if (sampleSize <= 0) {
sampleSize = 1;
}
options.inSampleSize = sampleSize;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(picturePath, options);
} catch (Exception e) {
return null;
}
}
三、生成二维码图片
主要类如下所示:
/**
* @desciption: 二维码生成
*/
public class CodeCreator {
/**
* 生成二维码
*/
public static Bitmap createQRCode(String content, int w, int h, Bitmap logo) {
if (TextUtils.isEmpty(content)) {
return null;
}
/*偏移量*/
int offsetX = w / 2;
int offsetY = h / 2;
/*生成logo*/
Bitmap logoBitmap = null;
if (logo != null) {
Matrix matrix = new Matrix();
float scaleFactor = Math.min(w * 1.0f / 5 / logo.getWidth(), h * 1.0f / 5 / logo.getHeight());
matrix.postScale(scaleFactor, scaleFactor);
logoBitmap = Bitmap.createBitmap(logo, 0, 0, logo.getWidth(), logo.getHeight(), matrix, true);
}
/*如果log不为null,重新计算偏移量*/
int logoW = 0;
int logoH = 0;
if (logoBitmap != null) {
logoW = logoBitmap.getWidth();
logoH = logoBitmap.getHeight();
offsetX = (w - logoW) / 2;
offsetY = (h - logoH) / 2;
}
/*指定为UTF-8*/
Hashtable hints = new Hashtable();
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
//容错级别
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
//设置空白边距的宽度
hints.put(EncodeHintType.MARGIN, 0);
// 生成二维矩阵,编码时指定大小,不要生成了图片以后再进行缩放,这样会模糊导致识别失败
BitMatrix matrix = null;
try {
matrix = new MultiFormatWriter().encode(content,
BarcodeFormat.QR_CODE, w, h, hints);
// 二维矩阵转为一维像素数组,也就是一直横着排了
int[] pixels = new int[w * h];
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
if (x >= offsetX && x < offsetX + logoW && y >= offsetY && y < offsetY + logoH) {
int pixel = logoBitmap.getPixel(x - offsetX, y - offsetY);
if (pixel == 0) {
if (matrix.get(x, y)) {
pixel = 0xff000000;
} else {
pixel = 0xffffffff;
}
}
pixels[y * w + x] = pixel;
} else {
if (matrix.get(x, y)) {
pixels[y * w + x] = 0xff000000;
} else {
pixels[y * w + x] = 0xffffffff;
}
}
}
}
Bitmap bitmap = Bitmap.createBitmap(w, h,
Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, w, 0, 0, w, h);
return bitmap;
} catch (WriterException e) {
System.out.print(e);
return null;
}
}
}