zxing开源库工作流程源码详解
代碼獲取
作為移動(dòng)客戶端開發(fā)者來說,對(duì)二維碼識(shí)別或二維碼生成相關(guān)的開發(fā)需求肯定并不陌生,Android開發(fā)二維碼相關(guān)的功能通常都會(huì)使用或參考大名鼎鼎的zxing庫。而本文則主要是通過源碼分析一下該開源庫掃描二維碼的工作流程,對(duì)這塊能有個(gè)更深的了解。
首先使用git將項(xiàng)目代碼clone到本地,新建項(xiàng)目,將zxing文件夾中的android以及core文件夾代碼覆蓋到對(duì)應(yīng)的目錄下,稍作一些修改即可運(yùn)行一個(gè)簡(jiǎn)單的二維碼掃描的示例應(yīng)用。
整體流程
Demo代碼運(yùn)行起來后,會(huì)進(jìn)入一個(gè)掃描的主功能界面,將掃描框?qū)?zhǔn)一個(gè)二維碼即可彈出解析結(jié)果信息的浮框。通過AndroidManifest.xml文件中可以得知這個(gè)頁面對(duì)應(yīng)的類為CaptureActivity.java,我們便從這個(gè)類開始,分析整個(gè)二維碼掃描的流程。
要分析一個(gè)Activity,當(dāng)然要從它的生命周期所對(duì)應(yīng)的各個(gè)方法說起。首先我們來看它的onCreate()方法:
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); } 復(fù)制代碼這個(gè)方法代碼不多,也很容易看懂,主要就是做一些初始化的工作。InactivityTimer主要是用來監(jiān)聽當(dāng)手機(jī)是使用電池而不是充電狀態(tài)時(shí),如果5分鐘內(nèi)沒有做任何操作,則主動(dòng)finish掉activity。BeepManager負(fù)責(zé)掃描到結(jié)果后震動(dòng)或鈴聲相關(guān),AmbientLightManager則是負(fù)責(zé)控制閃光燈。
繼續(xù)往下走看onResume()方法:
protected void onResume() {super.onResume();...// CameraManager must be initialized here, not in onCreate(). This is necessary because we don't// want to open the camera driver and measure the screen size if we're going to show the help on// first launch. That led to bugs where the scanning rectangle was the wrong size and partially// off screen.cameraManager = new CameraManager(getApplication());viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);viewfinderView.setCameraManager(cameraManager);...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);} } 復(fù)制代碼這個(gè)方法很重要,初始化了CameraManager,掃描二維碼毋庸置疑是需要用到相機(jī),通過相機(jī)預(yù)覽的一幀一幀的圖片,去解析上面可能存在的二維碼信息。而在最后面還初始化了SurfaceView,通過hasSurface來決定是走initCamera(surfaceHolder)還是surfaceHolder.addCallback(this)。在上面的onCreate()中我們可以看到hasSurface被初始化成false,所以這里走的應(yīng)該是else的代碼塊。CaptureActivity實(shí)現(xiàn)了SurfaceHolder.Callback接口,因此該方法綁定了surfaceHolder的回調(diào)。當(dāng)SurfaceView添加到 activity 中時(shí),會(huì)調(diào)用surfaceCreated():
public void surfaceCreated(SurfaceHolder holder) {if (holder == null) {Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!");}if (!hasSurface) {hasSurface = true;initCamera(holder);} } 復(fù)制代碼這里我們看到會(huì)改變hasSurface的狀態(tài),然后走initCamera(holder),和onResume()中 hasSurface為true時(shí)做的操作是一樣的:
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); } 復(fù)制代碼cameraManager 打開了驅(qū)動(dòng),并且把自己傳入一個(gè)CaptureActivityHandler對(duì)象中去,那這個(gè)CaptureActivityHandler看起來像是一個(gè)進(jìn)行消息通知的 Handler,它的具體作用又是什么呢?我們來看看它的構(gòu)造方法:
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(); } 復(fù)制代碼通過進(jìn)入CaptureActivityHandler.java可以看到該類確實(shí)繼承了Handler,并且在它的構(gòu)造方法中開啟了一個(gè)DecodeThread的線程,并且調(diào)用了cameraManager的startPreview()方法:
Asks the camera hardware to begin drawing preview frames to the screen.
開啟相機(jī)預(yù)覽后,再看下面的restartPreviewAndDecode():
private void restartPreviewAndDecode() {if (state == State.SUCCESS) {state = State.PREVIEW;cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);activity.drawViewfinder();} }public synchronized void requestPreviewFrame(Handler handler, int message) {OpenCamera theCamera = camera;if (theCamera != null && previewing) {previewCallback.setHandler(handler, message);theCamera.getCamera().setOneShotPreviewCallback(previewCallback);} } 復(fù)制代碼可以看到這個(gè) handler 會(huì)一直傳遞到一個(gè)previewCallback對(duì)象中去,而PreviewCallback是setOneShotPreviewCallback()方法的一個(gè)回調(diào),setOneShotPreviewCallback方法上的注釋說明:
Installs a callback to be invoked for the next preview frame in addition to displaying it on the screen. After one invocation, the callback is cleared. This method can be called any time, even when preview is live. Any other preview callbacks are overridden.
使用此方法注冊(cè)預(yù)覽回調(diào)接口時(shí),會(huì)將下一幀數(shù)據(jù)回調(diào)給onPreviewFrame()方法,調(diào)用完成后這個(gè)回調(diào)接口將被銷毀,也就是只會(huì)回調(diào)一次預(yù)覽幀數(shù)據(jù)。繼續(xù)順著這個(gè)方法走下去,看回調(diào)方法onPreviewFrame():
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");} } 復(fù)制代碼這里將返回的 byte 數(shù)組數(shù)據(jù)和預(yù)覽幀的寬高信息通過 handler 進(jìn)行通知,這個(gè) handler 就是上文中傳過來的decodeThread.getHandler(),previewMessage為R.id.decode,目的就是把圖片數(shù)據(jù)拿到該線程中進(jìn)行解析。我們跟進(jìn)到DecodeHandler.java中查看handleMessage()方法:
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;} }private void decode(byte[] data, int width, int height) {long start = System.currentTimeMillis();//省略具體解析代碼...Handler handler = activity.getHandler();//CaptureActivityHandlerif (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();}} } 復(fù)制代碼上面代碼很清晰,DecodeHandler 接收到R.id.decode的消息后,會(huì)調(diào)用decode()方法去解析傳過來的圖片數(shù)據(jù)。經(jīng)過一系列解析操作,得到結(jié)果。如果結(jié)果為不為空,則通過CaptureActivityHandler將解析成功的消息傳到CaptureActivity中進(jìn)行后續(xù)解析結(jié)果展示。而如果解析結(jié)果為空呢,說明二維碼信息解析失敗了,傳了一個(gè)R.id.decode_failed到CaptureActivityHandler中:
public void handleMessage(Message message) {switch (message.what) {...case 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);break;...} }復(fù)制代碼可以看到,解析失敗時(shí),重新調(diào)用requestPreviewFrame獲取下一幀預(yù)覽照片,再拿去解析,知道返回正確結(jié)果或者手動(dòng)退出。
整個(gè)過程的時(shí)序圖如下:
轉(zhuǎn)載于:https://juejin.im/post/5be29592e51d45222912c0a6
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的zxing开源库工作流程源码详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 修改用户权限
- 下一篇: 企业移动化诉求与开发者之间的矛盾