最近項目裏須要實現二維碼的掃描功能,掃描兩個二維碼而後獲得數據進行綁定。目前比較常見的二維碼掃描庫就是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 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。