1. 程式人生 > >二維碼識別開源專案zxing的使用和原始碼分析

二維碼識別開源專案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來實現
瞭解清楚整個流程,更利於我們以後的應用