zxing用于Java、Android的条形码扫描库,我们日常使用的APP中的二维码扫码功能绝大对数都是基于zxing项目做二次开发的,本文就此对zxing源码进行深入分析。
1.1 zxing 目录结构zxing github目录文件较多,但其实我们只需要查看android-core、android、core三个文件夹即可。
android-core:这里面只有一个类,包含配置Android摄像头的一些实用方法。android:Android扫码二维码的Demo,也就是需要我们重点分析的源码。core:纯java源码,可以理解为二维码生成和识别的核心包。 1.2 zxing - android 包目录:
book:搜索与展示书籍的相关类。camera: *** 作摄像头的相关类。clipboard:剪贴板。encode:编码功能的各个组件集合。history:扫描历史管理的相关类。result:扫码结果的相关类。share:将扫码结果分享出去。wifi:扫码自动连接WIFI。
Demo的功能模块较多,我们只重点分析camera模块和扫码流程相关类,在实际开发过程中,也只对这几个模块和类进行二次开发,其他次要模块都会被裁减掉
二 开启扫码流程分析扫码二维码的流程,需要先找到程序入口。我们从CaptureActivity开始分析。
CaptureActivity是Demo的逻辑核心类,包含了各个模块manager的实例化、初始化动作,以及加载预览控件和处理显示扫码结果。
CaptureActivity在onCreate中只做了初始化动作,我们直接看onResume()方法:
@Override protected void onResume() { super.onResume(); ··· //初始化CameraManager cameraManager = new CameraManager(getApplication()); //ViewfinderView是扫码框控件,传入cameraManager为了获取扫码框的尺寸 viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view); viewfinderView.setCameraManager(cameraManager); ... //SurfaceView是图像预览控件 SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view); SurfaceHolder surfaceHolder = surfaceView.getHolder(); if (hasSurface) { initCamera(surfaceHolder); } else { surfaceHolder.addCallback(this); } } @Override public void surfaceCreated(SurfaceHolder holder) { if (holder == null) { Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!"); } if (!hasSurface) { hasSurface = true; //在SurfaceHolder加载完成以后初始化摄像头。 initCamera(holder); } } private void initCamera(SurfaceHolder surfaceHolder) { ··· try { //打开摄像头 cameraManager.openDriver(surfaceHolder); // Creating the handler starts the preview, which can also throw a RuntimeException. if (handler == null) { //这个handler负责扫码流程的所有状态的传递 handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager); } decodeOrStoreSavedBitmap(null, null); } catch (IOException ioe) { ··· } catch (RuntimeException e) { ··· } }
在onResume和SurfaceHolder加载完成以后,调用了initCamera方法初始化摄像头,并且创建了一个CaptureActivityHandler,用来传递扫码流程中的所有状态。
上面代码可以看到,在CaptureActivity中调用了cameraManager.openDriver(surfaceHolder)来开启摄像头,于是我们来分析CameraManager类和摄像头模块。
2.1 启动摄像头摄像头模块用于管理camera,包括打开,关闭,配置预览参数,闪光灯等。
CameraFacing:枚举类,标明前置摄像头,后置摄像头。OpenCamera:表示已经打开的Camera以及它的元数据。OpenCameraInterface:用于打开Camera并获得数据。AutoFocusManager:Camera自动对焦相关。CameraConfigurationManager:用于读取,分析,设置Camera参数。CameraConfigurationUtils:主要用于配置camera参数。CameraManager:相机管理核心类, *** 作Camera的入口。FrontLightMode:枚举类, 表示闪光灯的开,关,自动。PreviewCallback:预览回调类。
注意,CameraConfigurationUtils原本是在android-core文件夹中的,在我的本地项目里为了方便查看,将CameraConfigurationUtils类直接copy到摄像头模块包中。CameraConfigurationUtils主要为CameraConfigurationManager服务的工具类。
CameraManager类封装了所有有关摄像头的 *** 作,所有外部模块想 *** 作摄像头都需要通过它。下面仅分析zxing的摄像头开启流程,关于摄像头具体 *** 作细节请自行查看源码和API。
public synchronized void openDriver(SurfaceHolder holder) throws IOException { OpenCamera theCamera = camera; if (theCamera == null) { //通过OpenCameraInterface打开摄像头 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); ··· } 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) { ··· } //设置摄像头预览控件载体 cameraObject.setPreviewDisplay(holder); }
CameraManager的openDriver方法,通过OpenCameraInterface打开摄像头,通过configManager初始化参数,并且在最后设置了摄像头预览控件载体,也就是说,openDriver方法执行完成之后,摄像头的开启和配置工作已经完成。那么在哪里启动了摄像头的预览呢?
我们还记得,在CaptureActivity的initCamera方法中,执行完openDriver方法后创建了一个CaptureActivityHandler对象,摄像头预览动作就执行在CaptureActivityHandler的构造函数里面。
2.2 获取一帧图像CaptureActivityHandler对象负责扫码流程中的所有状态的传递,CaptureActivityHandler在构造函数中启动了摄像头的预览动作,并且执行了一个解码线程。
CaptureActivityHandler(CaptureActivity activity, CollectiondecodeFormats, 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(); } ··· private void restartPreviewAndDecode() { if (state == State.SUCCESS) { state = State.PREVIEW; //请求摄像头的一帧图像数据,注意这里传入的是decodeThread的handler cameraManager.requestPreviewframe(decodeThread.getHandler(), R.id.decode); //重绘扫码框控件 activity.drawViewfinder(); } }
CaptureActivityHandler在开启摄像头预览以后,执行了restartPreviewAndDecode方法,通过cameraManager的requestPreviewframe方法请求摄像头的一帧图像数据。那么我们再来看一下cameraManager的requestPreviewframe方法:
public synchronized void requestPreviewframe(Handler handler, int message) { OpenCamera theCamera = camera; if (theCamera != null && previewing) { //传decodeThread的handler到previewCallback previewCallback.setHandler(handler, message); //请求camera的一帧预览画面 theCamera.getCamera().setOneShotPreviewCallback(previewCallback); } }
这里通过camera的setOneShotPreviewCallback方法,请求camera的一帧预览画面,并传入了previewCallback回调对象。我们看一下PreviewCallback的回调接口源码:
@Override public void onPreviewframe(byte[] data, Camera camera) { Point cameraResolution = configManager.getCameraResolution(); Handler thePreviewHandler = previewHandler; if (cameraResolution != null && thePreviewHandler != null) { //拿到一帧预览画面数据,回传给handler处理 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"); } }
获取一帧预览画面的流程:
通过CaptureActivityHandler启动一条解码线程DecodeThread。调用cameraManager.startPreview()启动摄像头预览。调用restartPreviewAndDecode方法,通过cameraManager的requestPreviewframe方法设置PreviewCallback回调接口,并传入DecodeThread的DecodeHandler对象。在onPreviewframe方法中拿到一帧预览数据,再通过之前传进来的DecodeHandler对象处理这一帧数据。
到这里我们已经拿到了一帧预览画面,并且已经交给了DecodeThread的DecodeHandler处理,那么很明显,DecodeThread就是用来将这一帧数据解码的线程。
2.3 图像解码流程DecodeThread是负责图像解码这样耗时动作的子线程,它内部的DecodeHandler负责具体的图像解码。我们来看DecodeThread的源码:
Handler getHandler() { //保证线程同步,防止handler为空。 try { handlerInitLatch.await(); } catch (InterruptedException ie) { // continue? } return handler; } @Override public void run() { Looper.prepare(); //创建一个执行在子线程的handler handler = new DecodeHandler(activity, hints); handlerInitLatch.countDown(); Looper.loop(); }
DecodeThread在run()方法中创建了一个执行在子线程的DecodeHandler,前文我们已经知道,一帧预览画面的数据就是通过这个DecodeHandler进行处理的,那么DecodeHandler是如何处理的呢?
@Override public void handleMessage(Message message) { if (message == null || !running) { return; } switch (message.what) { case R.id.decode: //一帧预览图像的数据就是先传递到这里 //decode进行图像解码 decode((byte[]) message.obj, message.arg1, message.arg2); break; case R.id.quit: running = false; Looper.myLooper().quit(); break; } } private void decode(byte[] data, int width, int height) { long start = System.currentTimeMillis(); //省略具体的解码过程 ··· //获取主线程的handler,解码完成的结果传递到主线程处理 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(); } } }
以上代码可以看到,图像数据在子线程的handler中完成了解码过程,不管成功还是失败,解码结果都会传递到主线程的handler中处理,下面来看主线程handler(CaptureActivityHandler)的处理过程:
@Override public void handleMessage(Message message) { switch (message.what) { case R.id.restart_preview: //重新取一帧图像并执行解码 restartPreviewAndDecode(); break; case R.id.decode_succeeded: //解码成功 state = State.SUCCESS; Bundle bundle = message.getData(); Bitmap barcode = null; float scaleFactor = 1.0f; if (bundle != null) { byte[] compressedBitmap = bundle.getByteArray(DecodeThread.BARCODE_BITMAP); if (compressedBitmap != null) { barcode = BitmapFactory.decodeByteArray(compressedBitmap, 0, compressedBitmap.length, null); // Mutable copy: barcode = barcode.copy(Bitmap.Config.ARGB_8888, true); } scaleFactor = bundle.getFloat(DecodeThread.BARCODE_SCALED_FACTOR); } //activity执行handleDecode方法。 activity.handleDecode((Result) message.obj, barcode, scaleFactor); break; case R.id.decode_failed: //解码失败 state = State.PREVIEW; //重新取一帧图像并执行解码 cameraManager.requestPreviewframe(decodeThread.getHandler(), R.id.decode); break; ··· } }
主线程handler处理执行结果,如果解码失败就再重新取一帧图像并执行解码,循环整个流程直到扫码成功;如果解码成功就在activity执行扫码成功的返回。至此,整个扫码流程就分析完成了。
三 总结zxing扫码流程:
- 初始化相机,设置一些相机参数;绑定SurfaceView,在SurfaceView上显示预览图像;获取相机的一帧图像,将图像数据传递到异步的handler中解析;对图像数据进行解析,解析成功进入下一步,不成功回到第3步;返回解析结果并退出
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)