二維碼識別開源專案zxing的使用和原始碼分析
引言
最近公司需要用到二維碼掃描識別的功能,回去翻看以前的使用,發現搞了很久都沒有弄明白。上網搜尋更是一堆雜亂的資訊,很難去抽離自己需要的資訊。於是,狠下心來跟著呼叫的思路,一步一步的分析原始碼。最後有種豁然開朗的感覺,哈哈
用法
1.1 新增core-3.0.0.jar
1.2 配置許可權
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
1.3 通過startActivityForResult 啟動CaptureActivity
Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
startActivityForResult(intent, REQUEST_CODE_SCAN);
1.4 在onActivityResult中接收回調
分析
1 CaptureActiivity分析
1.1 在onCreate中做了一些初始化工作
1.1.1 設定保持螢幕喚醒狀態
1.1.2 建立一個Timer:如果裝置使用電池供電,一段時間不活動之後結束activity
1.1.3建立一個BeepManager:主要用於掃描成功後提示聲的
1.2 在onResume中
1.2.1 初始化camera:使用CameraManager,這個類主要提供關於camera的一些基本操作
1.2.2 初始化ViewfinderView:覆蓋在預覽圖(SurfaceView)上方的一個view,主要作用是增加了部分透明的取景框和掃描動畫;我們可以根據需要改變取景框的大小形狀,改變掃描動畫等,即可以自定義
1.2.3 初始化SurfaceView
2 CaptureActivityHandler分析
2.1 在初始化camera時,建立了一個CaptureActivityHandler:
private void initCamera(SurfaceHolder surfaceHolder) {
if (surfaceHolder == null) {
throw new IllegalStateException("No SurfaceHolder provided");
}
if (cameraManager.isOpen()) {
return;
}
try {
// 開啟Camera硬體裝置
cameraManager.openDriver(surfaceHolder);
if (handler == null) {
handler = new CaptureActivityHandler(this, decodeFormats,
decodeHints, characterSet, cameraManager);
}
} catch (IOException ioe) {
Log.w(TAG, ioe);
displayFrameworkBugMessageAndExit();
} catch (RuntimeException e) {
Log.w(TAG, "Unexpected error initializing camera", e);
displayFrameworkBugMessageAndExit();
}
}
2.2 初始化過程分析
public 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;
this.cameraManager = cameraManager;
cameraManager.startPreview();
restartPreviewAndDecode();
}
我們來看下這幾句程式碼的執行:
2.2.1 啟動一個DecodeThread:用於解析二維碼的子執行緒,下一節再詳細分析
decodeThread = new DecodeThread(activity, decodeFormats, baseHints,characterSet, new ViewfinderResultPointCallback(activity.getViewfinderView()));
decodeThread.start();
2.2.2 cameraManager.startPreview();:開始拍攝預覽,內部主要就是呼叫了camera的startPreview()方法
2.2.3 restartPreviewAndDecode();:開始解碼,下邊進入這個方法分析
cameraManager.requestPreviewFrame(decodeThread.getHandler(),R.id.decode);我們詳細看這個方法的實現
1 previewCallback.setHandler(handler, message);
該PreviewCallback主要實現了Camera.PreviewCallback介面,提供了一個setHandler方法,當回撥onPreviewFrame這個方法時,通過設定的Handler來派發訊息2 theCamera.setOneShotPreviewCallback(previewCallback);:利用Camera物件上的這個方法註冊Camera.PreviewCallback,從而當下一幅預覽影象可用時呼叫一次onPreviewFrame
3 這兩步主要作用就是獲取一幀的資料,將這幀數字放在Message中,然後通過decodeThread.getHandler()獲取到的handler傳送出去;該封裝了幀資料的Message包含了一個值為R.id.decode的what(主要用於訊息的區分)
3 DecodeThread分析
3.1 DecodeThread繼承Thread,是一個專門用於解碼的執行緒
3.2 既然繼承Thread,我們可以集中看它的run()方法做了什麼操作
@Override
public void run() {
Looper.prepare();
handler = new DecodeHandler(activity, hints);
Log.i("test", "跑進來啦!!");
handlerInitLatch.countDown();
Looper.loop();
}
建立了一個DecodeHandler,該類才真正實現decode的功能
4 DecodeHandler分析
decodeThread.getHandler()獲取到的物件為DecodeHandler,該類繼承Handler,主要用於DecodeThread解碼執行緒的訊息分發和處理。下邊看下訊息的處理
@Override
public void handleMessage(Message message) {
if (!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;
}
}
4.1 通過對比message.what,會進入R.id.decode這個分支,這裡就是解碼真正實現的地方,下邊進去decode()這個方法
// 上邊是一堆解碼的程式碼,這裡不糾結
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();
}
}
4.2 看到解碼完成,會通過tivity.getHandler()獲取Handler物件,該物件的例項為CaptureActivityHandler,通過它去分發和處理訊息,這裡會傳送兩種訊息
5重入CaptureActivityHandler
分析訊息的處理(handleMessage()方法)
當為R.id.decode_succeeded時,這是解碼成功
1 生成一個bitmap
2 通過呼叫activity.handleDecode將bitmap 和Result物件返回給activity處理
當為R.id. decode_failed時,這是解碼失敗
繼續呼叫cameraManager.requestPreviewFrame(decodeThread.getHandler(),R.id.decode);方法獲取一幀數字,傳送給DecodeThread執行緒解碼,不斷重複該步驟
6 CountDownLatch使用
6.1 在DecodeThread建立了CountDownLatch:
handlerInitLatch = new CountDownLatch(1);
6.2 分析作用
public Handler getHandler() {
try {
handlerInitLatch.await();
} catch (InterruptedException ie) {
// continue?
}
return handler;
}
6.2.1 可以看到每次呼叫getHandler()時,都會呼叫handlerInitLatch.await();:呼叫此方法會一直阻塞當前執行緒,直到計時器的值為0;而建立CountDownLatch時,傳入的是1,所以計數器的值只會從1-0
6.2.2 那在哪裡呼叫呼叫了getHandler()方法呢?全域性搜尋下,有三個地方
1 CaptureActivity的onPause()方法中:主要為了停止訊息的派發
2 識別失敗、3restartPreviewAndDecode:重新預覽識別:
這兩個方法都是呼叫了cameraManager.requestPreviewFrame(decodeThread.getHandler(),R.id.decode);
這是重新掃描
於是,我猜測這裡使用CountDownLatch,主要是為了讓獲取到一幀資料前,先保證DecodeHandler已經被建立,可用於解碼
7 總結
其實,二維碼識別的功能已經封裝好了。對於一些介面的變化,我們可以修改CaptureActivity和ViewfinderView來實現
瞭解清楚整個流程,更利於我們以後的應用