最近项目里须要实现二维码的扫描功能,扫描两个二维码而后获得数据进行绑定。目前比较常见的二维码扫描库就是zxing和zbar了,zxing是google官方的开源项目,有专门的维护,java编写。zbar使用C语言写的,并且github上多年没有代码提交了,因此我决定选用zxing。java
附上zxing的项目地址:
zxing
打开zxing的github地址,发现彷佛没有如何接入的文档。没关心,没有文档,可是有demo,咱们要作的就是修改demo,移除无用的功能,只保留二维码的扫描和识别。android
下载项目后,里面不少东西咱们是不须要的,咱们须要的就是这个,如图所示git
screenshot.pnggithub
这个就是刚才所说的android的demo,新建一个android项目,将这个module导入工程并命名为zxinglib,在这个module里的gradle文件里添加依赖。api
dependencies{ api 'com.google.zxing:android-core:3.3.0' api 'com.google.zxing:core:3.3.2' }
运行这个module,你会发现这就是一个已经集成好zxing二维码扫描的app,同时还有一些不须要的功能,好比建立二维码,历史记录等等,并且仍是相机预览仍是横屏。下面咱们就分析一下这个demo的二维码识别流程app
首先打开AndroidManifest文件,找到含有ide
<intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter>
的Activity,发现是CaptureActivity,发现他还有好多其余intent-filter,CaptureActivity是能够被其余应用打开的,既然找到了入口,那就进去分析吧。
先看onCreate方法:函数
@Override public void onCreate(Bundle icicle) { super.onCreate(icicle); Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setContentView(R.layout.capture); hasSurface = false; inactivityTimer = new InactivityTimer(this); beepManager = new BeepManager(this); ambientLightManager = new AmbientLightManager(this); PreferenceManager.setDefaultValues(this, R.xml.preferences, false);}
window设置标志位,保证屏幕常亮不会黑屏,inactivityTimer保证在电量较低的时候且一段时间没有激活的时候,关闭CaptureActivity,
beepManager是用来扫码时发出声音和震动的,ambientLightManager是用来控制感光的,以此来控制闪光灯的开闭。而后咱们再来看布局文件:oop
<SurfaceView android:id="@+id/preview_view" android:layout_width="fill_parent" android:layout_height="fill_parent"/> <com.google.zxing.client.android.ViewfinderView android:id="@+id/viewfinder_view" android:layout_width="fill_parent" android:layout_height="fill_parent"/>
布局文件里比较重要的就是这两个,一个surfaceview和一个viewfinderView,一个是照相机用来预览的界面,一个是取景框的界面,剩下的控价都是用来展现扫描结果的,和咱们的需求没有太大关系,这里就不说了。布局
扫描二维码天然不能少的就是相机的调用了,在AndroidManifest文件里,咱们也看到了相关权限的声明
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.FLASHLIGHT"/>
有相机,震动和闪光灯的权限。
接着看一下是在哪里初始化相机并调用预览的,在onReumse里
有一段代码
SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view); SurfaceHolder surfaceHolder = surfaceView.getHolder(); if (hasSurface) { // 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 { // Install the callback and wait for surfaceCreated() to init the camera. surfaceHolder.addCallback(this); }
这里初始化了surfaceview,同时判断surface是否为true,true就调用initcamera,否在就为surfaceholder添加回调。刚才已经看到了,hasSurface在onCreate的时候赋值为false,以后在onResume也没有进行true的赋值,因此这里在第一次打开的时候,hasSurface=false。
那咱们就要关注surfaceHolder的回调了
@Override public void surfaceCreated(SurfaceHolder holder) { if (holder == null) { Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!"); } if (!hasSurface) { hasSurface = true; initCamera(holder); } }
在这里咱们发如今surface建立后仍是调用了initCamera,进入initCamera看看里面有什么
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(); } }
这里咱们关心两个点,cameraManager.openDriver(surfaceHolder)和CaptureActivityHandler,进入openDriver这个方法
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) { // Driver failed Log.w(TAG, "Camera rejected parameters. Setting only minimal safe-mode parameters"); Log.i(TAG, "Resetting to saved camera params: " + parametersFlattened); // Reset: if (parametersFlattened != null) { parameters = cameraObject.getParameters(); parameters.unflatten(parametersFlattened); try { cameraObject.setParameters(parameters); configManager.setDesiredCameraParameters(theCamera, true); } catch (RuntimeException re2) { // Well, darn. Give up Log.w(TAG, "Camera rejected even safe-mode parameters! No configuration"); } } } cameraObject.setPreviewDisplay(holder); }
这里能够发现,主要是针对camera的一些参数的设定,另外还要说明的一点是sdk21之后,之前的camera类已经废弃了,google又给出了camera2来替他他们,可是目前zxing这个库里尚未使用camera2,关于camera的相关问题,等之后有时间单独来写一篇文章,这里咱们主要针对的是流程的分析。这个方法里咱们发现调用了CameraConfigurationManager的initFromCameraParameters和setDesiredCameraParameters,这两个方法里找出了相机预览的最佳大小和根据屏幕进行camera方向的旋转,感兴趣的话能够看一下这两个方法。
接下来咱们来看CaptureActivityHandler
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,再看看DeocodeThread的run方法
@Override public void run() { Looper.prepare(); handler = new DecodeHandler(activity, hints); handlerInitLatch.countDown(); Looper.loop(); }
这里又实例化了一个DeoceHandler,好,那就进入DeoceHandler,看看这货又是什么
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(); } } }
这个decode方法,就是咱们心心念念的用来解析二维码的地方,multiFormatReader.decodeWithState(bitmap);获得结果后,返回给CaptureActivityHandler,captureActivityHandler在接到R.id.decode_succeededde message后,会调用CaptureActivity的handleDecode方法,在这里会调用
// Put up our own UI for how to handle the decoded contents.
private void handleDecodeInternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode) {
maybeSetClipboard(resultHandler); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); if (resultHandler.getDefaultButtonID() != null && prefs.getBoolean(PreferencesActivity.KEY_AUTO_OPEN_WEB, false)) { resultHandler.handleButtonPress(resultHandler.getDefaultButtonID()); return; } statusView.setVisibility(View.GONE); viewfinderView.setVisibility(View.GONE); resultView.setVisibility(View.VISIBLE); ImageView barcodeImageView = (ImageView) findViewById(R.id.barcode_image_view); if (barcode == null) { barcodeImageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.launcher_icon)); } else { barcodeImageView.setImageBitmap(barcode); } TextView formatTextView = (TextView) findViewById(R.id.format_text_view); formatTextView.setText(rawResult.getBarcodeFormat().toString()); TextView typeTextView = (TextView) findViewById(R.id.type_text_view); typeTextView.setText(resultHandler.getType().toString()); DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); TextView timeTextView = (TextView) findViewById(R.id.time_text_view); timeTextView.setText(formatter.format(rawResult.getTimestamp())); TextView metaTextView = (TextView) findViewById(R.id.meta_text_view); View metaTextViewLabel = findViewById(R.id.meta_text_view_label); metaTextView.setVisibility(View.GONE); metaTextViewLabel.setVisibility(View.GONE); Map<ResultMetadataType,Object> metadata = rawResult.getResultMetadata(); if (metadata != null) { StringBuilder metadataText = new StringBuilder(20); for (Map.Entry<ResultMetadataType,Object> entry : metadata.entrySet()) { if (DISPLAYABLE_METADATA_TYPES.contains(entry.getKey())) { metadataText.append(entry.getValue()).append('\n'); } } if (metadataText.length() > 0) { metadataText.setLength(metadataText.length() - 1); metaTextView.setText(metadataText); metaTextView.setVisibility(View.VISIBLE); metaTextViewLabel.setVisibility(View.VISIBLE); } } CharSequence displayContents = resultHandler.getDisplayContents(); TextView contentsTextView = (TextView) findViewById(R.id.contents_text_view); contentsTextView.setText(displayContents); int scaledSize = Math.max(22, 32 - displayContents.length() / 4); contentsTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, scaledSize); TextView supplementTextView = (TextView) findViewById(R.id.contents_supplement_text_view); supplementTextView.setText(""); supplementTextView.setOnClickListener(null); if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean( PreferencesActivity.KEY_SUPPLEMENTAL, true)) { SupplementalInfoRetriever.maybeInvokeRetrieval(supplementTextView, resultHandler.getResult(), historyManager, this); } int buttonCount = resultHandler.getButtonCount(); ViewGroup buttonView = (ViewGroup) findViewById(R.id.result_button_view); buttonView.requestFocus(); for (int x = 0; x < ResultHandler.MAX_BUTTON_COUNT; x++) { TextView button = (TextView) buttonView.getChildAt(x); if (x < buttonCount) { button.setVisibility(View.VISIBLE); button.setText(resultHandler.getButtonText(x)); button.setOnClickListener(new ResultButtonListener(resultHandler, x)); } else { button.setVisibility(View.GONE); } } }
用来展现最终的结果。
那么DeoceHandler又是何时调用deocode方法的呢?
@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; } }
DeoceHandler在收到what==R.id.deocde的message时会调用decode方法,那么是谁发送的这个message呢?还记得CaptureActivityHandler的构造函数里,调用了restartPreviewAndDeocode方法
private void restartPreviewAndDecode() { if (state == State.SUCCESS) { state = State.PREVIEW; cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode); activity.drawViewfinder(); }
这个方法里不只调用了drawViewFinder,绘制了取景框,还调用了
cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);这里传入了decodehandler,它又作了什么?
public synchronized void requestPreviewFrame(Handler handler, int message) { OpenCamera theCamera = camera; if (theCamera != null && previewing) { previewCallback.setHandler(handler, message); theCamera.getCamera().setOneShotPreviewCallback(previewCallback); } }
原来这个方法为camera设置了previewcallback,同时previewcallback还持有decodehandler的引用,这个previewcallback的
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"); }
}
在这里Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
cameraResolution.y, data);
message.sendToTarget();
发送了what=previewMessage,而这个previewMessage就是以前CaptureActivityHandler传入的R.id.decode。
那么camera的setOneShotPreviewCallback这个方法是用来干什么的?查看源码看注释
单个预览帧将被返回给提供的处理程序。 数据将以byte []形式到达在message.obj字段中,宽度和高度编码为message.arg1message.arg2。
至此一个从预览到识别解析的流程差很少就分析完了,围绕这些,那些demo里的不须要的东西就能够删除了。
最后附上git地址:
github
做者:滑板上的老砒霜 连接:https://www.jianshu.com/p/a4ba10da4231 來源:简书 简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。