Android二维码扫一扫功能实现,解析Zxing源码的执行过程
2017-11-27 00:00
756 查看
今日科技快讯
日前,第五届中国移动合作伙伴大会在广州召开,5G商用的步伐日益临近,随着政策支持与企业的境外布局,我国5G产业建设已走在世界前列,有望成为全球5G领跑者,国内部分5G研发企业已跻身全球第一梯队。在大会上,5G研发应用的最新成果亮相。首批国内5G高频基站功放、射频前端器件,以及业界首款5G终端射频器件模组原型产品等。
作者简介
各位小伙伴们大家好,新的一周又开始了,希望大家都能有个好心情迎接新的一周。
本篇来自 宇宝守护神[b][b][b][b][b][b][b][b][b][b] [/b][/b][/b][/b][/b][/b][/b][/b][/b][/b] 的投稿,分享了
Android 中基于 zxing 的扫码功能实现,希望大家喜欢!
[b][b]宇宝守护神[/b] [/b][b][b][b][b][b][b][b][b][b][b] [/b][/b][/b][/b][/b][/b][/b][/b][/b][/b]的博客地址:
http://my.csdn.net/qq_34902522
开始
建议阅读本文的同学,结合 zxing 的源码理解。
之前博客说明 zxing 的使用方式,并大致说了 IntentIntegrator 这个辅助类的作用,及内部的部分源码讲解。通过上篇博文的讲解,虽然我们成功使用了 zxing 的扫码功能,但是我们发现它的界面是这样的:
这显然不是我们想要的效果。所以我们必须要对 zxing 库进行修改,变成我们项目所要的扫码库。 那现在我们打算实现一个样式类似于微信扫一扫样子的二维码。大多数项目的界面应该跟这个差不多。该怎么下手呢?我们看一下微信扫一扫的效果:
Zxing扫码流程分析
我们首先分析一波zxing扫码的整个流程。我们知道想实现上面的界面效果,主要的布局的变化,扫码的核心算法与思路应该是跟 zxing 原来一样的。而且 zxing 的库是比较庞大的,我们只是实现扫码功能的话,zxing 里面的很多东西,我们是用不到的,所以需要对其简化,去掉不用的东西。 首先我们看 CaptureActivity
这个类,上篇文章也有提到过这个类,这个 Activity 就是官方的扫码界面。我们看他的 setContentView(R.layout.capture); 这行语句,进入 capture 布局,可以看到,以下眼熟的控件。CaptureActivity 里面有一个很重要的方法。如下:
private void initCamera(SurfaceHolder surfaceHolder) { if (surfaceHolder == null) { throw new IllegalStateException("No SurfaceHolder provided"); } if (cameraManager.isOpen()) { Log.w(TAG, "initCamera() while already open -- late SurfaceView callback?"); return; } try { cameraManager.openDriver(surfaceHolder); // Creating the handler starts the preview, which can also throw a RuntimeException. if (handler == null) { handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager); } decodeOrStoreSavedBitmap(null, null); } catch (IOException ioe) { Log.w(TAG, ioe); displayFrameworkBugMessageAndExit(); } catch (RuntimeException e) { // Barcode Scanner has seen crashes in the wild of this variety: // java.?lang.?RuntimeException: Fail to connect to camera service Log.w(TAG, "Unexpected error initializing camera", e); displayFrameworkBugMessageAndExit(); } }
这个 initCamera 方法涉及到相机的初始化配置,以及扫码配置与启动。CameraManager是相机管理类,里面有着很多很重要的方法,比如开始预览的方法,停止预览以及获取每一帧画面的数据信息等方法。我们先看 cameraManager.openDriver(surfaceHolder); 这行语句是,点击进去:
/** * Opens the camera driver and initializes the hardware parameters. * * @param holder The surface object which the camera will draw preview frames into. * @throws IOException Indicates the camera driver failed to open. */ public synchronized void openDriver(SurfaceHolder holder) throws IOException { OpenCamera theCamera = camera; if (theCamera == null) { theCamera = OpenCameraInterface.open(requestedCameraId); if (theCamera == null) { throw new IOException("Camera.open() failed to return object from driver"); } camera = theCamera; } if (!initialized) { initialized = true; configManager.initFromCameraParameters(theCamera); if (requestedFramingRectWidth > 0 && requestedFramingRectHeight > 0) { setManualFramingRect(requestedFramingRectWidth, requestedFramingRectHeight); requestedFramingRectWidth = 0; requestedFramingRectHeight = 0; } } Camera cameraObject = theCamera.getCamera(); Camera.Parameters parameters = cameraObject.getParameters(); String parametersFlattened = parameters == null ? null : parameters.flatten(); // Save these, temporarily try { configManager.setDesiredCameraParameters(theCamera, false); } catch (RuntimeException re) {
点看后我们看到描述的很清楚,这个方法的作用是打开相机设备,并且配置一些相机参数的。OpenCamera 是 Camera 的包装类。CameraConfigurationManager 是设置相机硬件参数的一个类。configManager.initFromCameraParameters(theCamera);这个方法主要是的内容是寻找最好的预览尺寸。寻找最佳预览尺寸的逻辑我就不说了,这块,可以看下这位兄弟写的 :
http://iluhcm.com/2016/01/08/scan-qr-code-and-recognize-it-from-picture-fastly-using-zxing/
里面说明了寻找最佳预览尺寸的逻辑,及优化。
configManager.setDesiredCameraParameters(theCamera, false); 这个方法主要就是设置我们想要的相机参数了。这里会把上面方法中找到的最佳预览大小 bestPreviewSize 设置给parameters.setPreviewSize(bestPreviewSize.x,
bestPreviewSize.y); 我们也可以在这个方法里面调用camera.setDisplayOrientation(90); 来实现竖屏的效果。 以上是 initCamera() 方法里面的 cameraManager.openDriver 这一块分析,接着我们来看 handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager); 语句。进入进去代码如下:
CaptureActivityHandler(CaptureActivity activity, Collection<BarcodeFormat> decodeFormats, Map<DecodeHintType,?> 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(); }
这个方法中我们看到 decodeThread 线程,我们进去看一下发现里面的代码主要是设置了Map:
@Override public void run() { Looper.prepare(); handler = new DecodeHandler(activity, hints); handlerInitLatch.countDown(); Looper.loop(); }
run 方法里面主要是创建了一个 decodeHandler 对象,并把 hints 这个存储支持扫码类型的变量给传进去了。我们接着看 decodeHandler 是什么鬼?
DecodeHandler(CaptureActivity activity, Map<DecodeHintType,Object> hints) { multiFormatReader = new MultiFormatReader(); multiFormatReader.setHints(hints); this.activity = activity; } @Override public void handleMessage(Message message) { if (message == null || !running) { return; } if (message.what == R.id.decode) { decode((byte[]) message.obj, message.arg1, message.arg2); } else if (message.what == R.id.quit) { running = false; Looper.myLooper().quit(); } }
代码很好理解,首先创建了一个 MultiFormatReader,并把支持扫码格式传给他,MultiFormatReader 是专门解密的一个核心类。很重要。然后我们看到当该 Handler 收到R.id.decode 改消息的时候,会调用 decode((byte[]) message.obj, message.arg1,
message.arg2); 这个方法,我们看下:
private void decode(byte[] data, int width, int height) { long start = System.currentTimeMillis(); Result rawResult = null; PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height); if (source != null) { BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); try { rawResult = multiFormatReader.decodeWithState(bitmap); } catch (ReaderException re) { // continue } finally { multiFormatReader.reset(); } } Handler handler = activity.getHandler(); if (rawResult != null) { // Don't log the barcode contents for security. long end = System.currentTimeMillis(); Log.d(TAG, "Found barcode in " + (end - start) + " ms"); if (handler != null) { Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult); Bundle bundle = new Bundle(); bundleThumbnail(source, bundle); message.setData(bundle); message.sendToTarget(); } } else { if (handler != null) { Message message = Message.obtain(handler, R.id.decode_failed); message.sendToTarget(); } } }
O(∩_∩)O哈!找了半天终于找到了,这方法重要了,这就是我们扫码逻辑中最重要的解密的逻辑了。代码虽然多但是并不难。首先它构建了一个 PlanarYUVLuminanceSource 对象,接着根据 source 创建了二进制的 BinaryBitmap。然后 rawResult =
multiFormatReader.decodeWithState(bitmap); 通过该语句,实现了解密,把解码的结果封装赋值给了Result类。最后把结果传给了 CaptureActivityHandler,在其 handlemessage方法中实现对结果的处理。在这里要注意一个问题,就是需要把传进来的 data 数据中的数据旋转一下,这里的数据是横屏的画面数据。需要转化为竖屏画面数据。该方法传进来的width,height 这两个参数的值也需要调换一下。具体的转化代码,可以看 YZxing-lib 库DecodeHandler
类里的实现。 我们现在想一个问题,就是 decode 这个方法是在什么时候实现的呢?也就是说 decodeHandler 是在什么时候发送了 R.id.decode 这个消息?我们看这个方法:
CaptureActivityHandler(CaptureActivity activity, Collection<BarcodeFormat> decodeFormats, Map<DecodeHintType,?> 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(); }
这个方法里面的 :
cameraManager.startPreview(); restartPreviewAndDecode();
这两行语句我们还没看呢。首先看第一行语句,很好理解,这是开始预览画面的执行语句。第二句是 restartPreviewAndDecode(); 我们进去看一下:
if (state == State.SUCCESS) { state = State.PREVIEW; cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode); activity.drawViewfinder(); }
这里我们看到了 R.id.decode 这个消息的 what 值。我们看 cameraManager 的 requestPreviewFrame 方法:
public synchronized void requestPreviewFrame(Handler handler, int message) { OpenCamera theCamera = camera; if (theCamera != null && previewing) { previewCallback.setHandler(handler, message); theCamera.getCamera().setOneShotPreviewCallback(previewCallback); } }
这里是获取预览界面的一帧。我们看 previewCallback 里面的代码:
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"); } }
挖了这么久终于找到了,onPreviewFrame 方法里,在这 decodeHandler 发送了解码的消息,并把一帧的图像数据发送了过去。如果 decodeHandler 里面的 decode 方法扫码失败的话,就发送一个 R.id.decode_failed 消息给 CaptureActivityHandler,CaptureActivityHandler
里会调用:
} else if (message.what == R.id.decode_failed) {// We're decoding as fast as possible, so when one decode fails, start another. state = State.PREVIEW; cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
该方法,继续请求下一帧的画面数据,去解析。分析到此,zxing 的扫码流程,大致的脉络就是这个样子。这里总结一下吧,就是点击扫码,跳转到 CaptureActivity,CaptureActivity里面调用了 initCamera 方法,该方法中一方面通过 cameraManager.openDriver(surfaceHolder);
对相机进行初始化,及硬件配置;一方面通过对 CaptureActivityHandler 的创建,实现解码类 MultiFormatReader 的配置,画面的预览实现,每一帧画面的数据请求,传递,解码逻辑实现。最后根据这一帧画面数据扫码结果 是成功还是失败发送,来决定是继续请求下一帧的画面信息还是处理扫码成功的结果。
YZxing-lib
在观察 CaptureActivity 的时候,我们发现了一个自定义控件,叫做 ViewfinderVIew ,通过阅读其代码,发现这就是绘制扫码框样式的地方。那我们在修改 zxing 库的时候就可以重写这个类,来实现对扫码框样式的修改。
YZxing-lib 这个库,是我基于 zxing 库修改的扫码库,去除了原来 Zxing 库中多余的部分,并对扫码效率进行了优化。我们先来看一下 YZxing 库的实现效果:
演示效果图,弹窗逻辑已删除:
扫码成功后,结果的回调:
微信的扫一扫,它聚焦框内有一条不断从上到下移动的绿线,我这边没做成他那样(比较懒),我这边实现的效果是跟 zxing sample 效果类似,是一条绿色的,一闪一闪的激光线。想实现微信它那种一条绿线从上到下不停移动的效果的话,让UI设计一张“绿线图片”(好拗口)设为 ImageView 的背景,通过 Animation 补间动画就可以实现了。看过效果图之后这里就介绍一下 YZxing-lib 的结构,方便大家看源码。
callback 包里面是请求每帧画面数据信息的回调。camera 包是相机相关的类,具体类的介绍这里不再赘述,大家也可以进YZxing-lib源码看,有详细说明。decode 包下主要是解码这块功能的类,以及扫码结果的处理。scannerView 相当于 zxing 里面的 viewfinderview,在这个类里实现了扫码界面的样式绘制。
使用方式
首先通过在 build.gradle 文件中添加如下编译语句将 YZxing-lib 库添加到项目中。
compile 'com.yangy:YZxing-lib:1.1'
或者在直接把GitHub上面的 YZxing 库下载下来,添加到项目中。 然后在点击跳转到扫码界面的点击事件中,调用如下方法:
Intent intent = new Intent(this, ScannerActivity.class); //这里可以用intent传递一些参数,比如扫码聚焦框尺寸大小,支持的扫码类型。 // //设置扫码框的宽 // intent.putExtra(Constant.EXTRA_SCANNER_FRAME_WIDTH, 400); // //设置扫码框的高 // intent.putExtra(Constant.EXTRA_SCANNER_FRAME_HEIGHT, 400); // //设置扫码框距顶部的位置 // intent.putExtra(Constant.EXTRA_SCANNER_FRAME_TOP_PADDING, 100); // Bundle bundle = new Bundle(); // //设置支持的扫码类型 // bundle.putSerializable(Constant.EXTRA_SCAN_CODE_TYPE, mHashMap); // intent.putExtras(bundle); startActivityForResult(intent, RESULT_REQUEST_CODE);
这里可以使用intent传递一些配置参数。支持有设置扫码框的大小及位置;设置支持的扫码类型。目前支持的自定义配置不多,后续有机会再扩充。 跳转的时候要有startActivityForResult 来跳转,这样在扫码成功之后,返回的结果可以在 onActivityResult方法中处理代码如下:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK) { switch (requestCode) { case RESULT_REQUEST_CODE: if (data == null) return; String type = data.getStringExtra(Constant.EXTRA_RESULT_CODE_TYPE); String content = data.getStringExtra(Constant.EXTRA_RESULT_CONTENT); Toast.makeText(MainActivity.this,"codeType:" + type + "-----content:" + content,Toast.LENGTH_SHORT).show(); break; default: break; } } super.onActivityResult(requestCode, resultCode, data); }
优化问题
基于 zxing 的二维码扫码可能会出现扫码速率比较低的问题。这里我所用的几点解决方法:
zxing 源码是截取的扫码聚焦框里面的图像数据信息来解码,这里可以改成获取全屏的图像信息。实现代码如下:
public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) { return new PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false); }
尽量减少支持的扫码类型。zxing 源码默认是支持所有的扫码类型。我们项目中使用的话,一般不需要支持这么多。仅支持 BarcodeFormat.QR_CODE(二维码)、BarcodeFormat.CODE_128(一维码)就可以应对很多场景了。
添加 hints.put(DecodeHintType.TRY_HARDER, true);语句,能够提高扫码精确度,准确率。
这三点是我在使用的,并且取得很大的效果的方法。还有一些提高的扫码速率的方法我就不细说了,这里推荐一篇文章写的蛮好的。
扫码优化策略:
http://iluhcm.com/2016/01/08/scan-qr-code-and-recognize-it-from-picture-fastly-using-zxing/
总结
在看源码的过程中,别想着一下能看明白,得慢慢看慢慢琢磨,实在想不明白的地方,就别去纠结了,过段时间再去看你当时迷惑的地方,可能就会想明白了。最后附上项目的地址,觉得还不错就start下吧(^__^) 。
YZxing项目地址:
https://github.com/MRYangY/YZxing
欢迎长按下图 -> 识别图中二维码
或者 扫一扫 关注我的公众号
相关文章推荐
- Android二维码功能实现,在程序内嵌入ZXing项目
- Android二维码功能实现,在程序内嵌入ZXing项目
- Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能(附源码)
- Android二维码功能实现,在程序内嵌入ZXing项目
- android开发之集成zxing,二维码,以及扫描二维码的功能实现。带源代码下载
- 实现android注册登陆功能的客户端服务器源码与解析socket
- android使用zxing二维码扫一扫功能
- Android zxing实现二维码生成和解析
- 加入数据库mysql实现android注册登陆功能的客户端服务器源码与解析
- Android二维码功能实现,在程序内嵌入ZXing项目
- EasyPusher实现安卓Android手机直播推送同步录像功能(源码解析)
- Android二维码功能实现,在程序内嵌入ZXing项目
- [转]EasyPusher实现安卓Android手机直播推送同步录像功能(源码解析)
- Android开源库之使用ZXing开源库实现二维码功能
- Android 二维码功能实现,在程序内嵌入ZXing项目
- Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能(附源码)
- Android二维码功能实现,在程序内嵌入ZXing项目
- [置顶] android开发之集成zxing,二维码,以及扫描二维码的功能实现。带源代码下载
- Android二维码功能实现,在程序内嵌入ZXing项目
- Android二维码功能实现,在程序内嵌入ZXing项目