Android掃描二維碼SimpleZxing源碼解析

源碼地址

谷歌官方有一個關(guān)于二維碼的zxing項目,地址為zxing,但是這個庫對于安卓應(yīng)用來說太大了。有一個開發(fā)者將這個庫進(jìn)行了簡化,地址為SimpleZxing,使得可以非常方便地使用在安卓工程中。

源碼解析

我們可以通過以下代碼來跳轉(zhuǎn)到掃描二維碼界面:

    private void startCaptureActivityForResult() {
        Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
        Bundle bundle = new Bundle();
        bundle.putBoolean(CaptureActivity.KEY_NEED_BEEP, CaptureActivity.VALUE_BEEP);
        bundle.putBoolean(CaptureActivity.KEY_NEED_VIBRATION, CaptureActivity.VALUE_VIBRATION);
        bundle.putBoolean(CaptureActivity.KEY_NEED_EXPOSURE, CaptureActivity.VALUE_NO_EXPOSURE);
        bundle.putByte(CaptureActivity.KEY_FLASHLIGHT_MODE, CaptureActivity.VALUE_FLASHLIGHT_OFF);
        bundle.putByte(CaptureActivity.KEY_ORIENTATION_MODE, CaptureActivity.VALUE_ORIENTATION_AUTO);
        bundle.putBoolean(CaptureActivity.KEY_SCAN_AREA_FULL_SCREEN, CaptureActivity.VALUE_SCAN_AREA_FULL_SCREEN);
        bundle.putBoolean(CaptureActivity.KEY_NEED_SCAN_HINT_TEXT, CaptureActivity.VALUE_SCAN_HINT_TEXT);
        intent.putExtra(CaptureActivity.EXTRA_SETTING_BUNDLE, bundle);
        startActivityForResult(intent, CaptureActivity.REQ_CODE);
    }

很容易就能看出,這里僅僅是傳遞了一些參數(shù),跳轉(zhuǎn)到了一個新的activity,那么我們進(jìn)入到這個新的activity中去查看。由于onCreate中沒有重要的代碼,所以我們查看onResume方法。

@Override
    protected void onResume() {
        super.onResume();
        if (orientationMode == VALUE_ORIENTATION_AUTO) {
            myOrientationDetector.enable();
        }
        cameraManager = new CameraManager(getApplication(), needExposure, needFullScreen);
        //viewfinderView實(shí)際上就是我們需要繪出的掃描二維碼的框,以及正在掃描的線
        viewfinderView = findViewById(R.id.viewfinder_view);
        viewfinderView.setCameraManager(cameraManager);
        viewfinderView.setNeedDrawText(needScanHintText);
        viewfinderView.setScanAreaFullScreen(needFullScreen);
        handler = null;
        beepManager.updatePrefs();
        if (ambientLightManager != null) {
            ambientLightManager.start(cameraManager);
        }
        //用來實(shí)時顯示相機(jī)的圖像
        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.
            //初始化并且打開相機(jī)
            initCamera(surfaceHolder);
        } else {
            // Install the callback and wait for surfaceCreated() to init the camera.
            //
            surfaceHolder.addCallback(this);
        }

    }

與之對應(yīng)的xml界面為:

    <SurfaceView
        android:id="@+id/preview_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.acker.simplezxing.view.ViewfinderView
        android:id="@+id/viewfinder_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

看到這里,我想大家應(yīng)該明白了。用SurfaceView來顯示相機(jī)的圖像,然后用viewfinderView來畫出一個藍(lán)色的掃描矩形、掃描射線等。這樣就能夠?qū)ο鄼C(jī)進(jìn)行定制,接下來,我們來看一下其中的細(xì)節(jié)吧~

首先來看前文中提到的initCamera方法。

private void initCamera(SurfaceHolder surfaceHolder) {
        if (surfaceHolder == null) {      //如果用來顯示相機(jī)圖像的容器為null的話,就跑出異常
            throw new IllegalStateException("No SurfaceHolder provided");
        }
        if (cameraManager.isOpen()) {      //如果已經(jīng)開啟了相機(jī),那么就返回
            //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, cameraManager);
            }
        } catch (Exception e) {
            //Log.w(TAG, e);
            returnResult(RESULT_CANCELED, getString(R.string.msg_camera_framework_bug));
        }
    }


public synchronized void openDriver(SurfaceHolder holder) throws IOException {
        OpenCamera theCamera = camera;
        if (theCamera == null) {
            //使用系統(tǒng)api,打開相機(jī)
            theCamera = OpenCameraInterface.open(OpenCameraInterface.NO_REQUESTED_CAMERA);
            if (theCamera == null) {
                throw new IOException("Camera.open() failed to return object from driver");
            }
            camera = theCamera;
        }

        if (!initialized) {
            //設(shè)置掃描二維碼的區(qū)域位置和大小
            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");
                }
            }
        }
        //將相機(jī)獲取的畫面展示在容器中
        cameraObject.setPreviewDisplay(holder);

    }

這里我們就知道了如何將相機(jī)獲取的畫面展示在容器中,那么現(xiàn)在最重要的問題也就是:怎么樣才能夠?qū)崟r地獲取照片,并且檢測照片中是否存在二維碼呢?讓我們回到initCamera方法中找答案,可以看到另一個重要的代碼:handler = new CaptureActivityHandler(this, cameraManager); 跟進(jìn)去構(gòu)造函數(shù):

CaptureActivityHandler(CaptureActivity activity, CameraManager cameraManager) {
        this.activity = activity;
        //創(chuàng)建并開啟一個專門用來檢測二維碼的線程,防止主線程卡頓
        decodeThread = new DecodeThread(activity, new ViewfinderResultPointCallback(activity.getViewfinderView()));
        decodeThread.start();
        state = State.SUCCESS;
        // Start ourselves capturing previews and decoding.
        this.cameraManager = cameraManager;
        //在startPreview方法執(zhí)行之后,SurfaceView才真的開始顯示照相機(jī)內(nèi)容
        cameraManager.startPreview();
        //重要方法
        restartPreviewAndDecode();
    }
    private void restartPreviewAndDecode() {
        if (state == State.SUCCESS) {
            state = State.PREVIEW;
            cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
            //重新繪制藍(lán)色邊緣矩形、掃描線等
            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);
        }
    }

可以看出來,setOneShotPreviewCallback方法將照相機(jī)的某一時刻的截屏發(fā)送給DecodeHandler,所以接下來的邏輯應(yīng)該在它的handleMessage方法中:

@Override
    public void handleMessage(Message message) {
        if (!running) {
            return;
        }
        if (message.what == R.id.decode) {
            decode((byte[]) message.obj, message.arg1, message.arg2);

        } else if (message.what == R.id.quit) {
            running = false;
            Looper.myLooper().quit();

        }
    }

正常情況下,邏輯應(yīng)該跳轉(zhuǎn)到decode方法中:

private void decode(byte[] data, int width, int height) {
        long start = System.currentTimeMillis();
        if (width < height) {
            // portrait
            byte[] rotatedData = new byte[data.length];
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++)
                    rotatedData[y * width + width - x - 1] = data[y + x * height];
            }
            data = rotatedData;
        }
        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);
                message.sendToTarget();
            }
        } else {
            if (handler != null) {
                Message message = Message.obtain(handler, R.id.decode_failed);
                message.sendToTarget();
            }
        }
    }

無論這個圖片中是否被掃描到了二維碼,都會轉(zhuǎn)移到activity.getHandler()方法所獲取的CaptureActivityHandler類中,所以看看它的handleMessage方法。

    @Override
    public void handleMessage(Message message) {
        if (message.what == R.id.decode_succeeded) {
            state = State.SUCCESS;
            //檢索二維碼成功時
            activity.handleDecode((Result) message.obj);

        } else if (message.what == R.id.decode_failed) {// We're decoding as fast as possible, so when one decode fails, start another.
            state = State.PREVIEW;
            //當(dāng)沒有檢索到二維碼時,重新進(jìn)行剛才我們所分析的流程
            cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);

        }
    }

    public void handleDecode(Result rawResult) {
        //播放掃描成功的聲音
        beepManager.playBeepSoundAndVibrate();
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //將掃描結(jié)果返回到上一個頁面
        returnResult(RESULT_OK, rawResult.getText());
    }

哈,這樣就把兩個最重要的部分分析完了!另一個我想提的是,這個代碼中有自動對焦的功能,它的實(shí)現(xiàn)方法為:

public synchronized void setTorch(boolean newSetting) {
        OpenCamera theCamera = camera;
        if (theCamera != null) {
            if (newSetting != configManager.getTorchState(theCamera.getCamera())) {
                boolean wasAutoFocusManager = autoFocusManager != null;
                if (wasAutoFocusManager) {
                    autoFocusManager.stop();
                    autoFocusManager = null;
                }
                configManager.setTorch(theCamera.getCamera(), newSetting);
                if (wasAutoFocusManager) {
                    //實(shí)現(xiàn)自動對焦
                    autoFocusManager = new AutoFocusManager(theCamera.getCamera());
                    autoFocusManager.start();
                }
            }
        }
    }

希望大家有所收獲~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,174評論 25 708
  • 摘要 最近,在公司項目上需要加入“二維碼掃描”的功能(Android端),筆者在網(wǎng)上查閱了一些資料,實(shí)現(xiàn)了這個功能...
    夢想編織者灬小楠閱讀 41,547評論 76 158
  • 前言 現(xiàn)在的應(yīng)用中二維碼掃描已經(jīng)成為一個應(yīng)用必不可少的功能,現(xiàn)在大部分Android二維碼掃描都是基于zxing和...
    g小志閱讀 5,398評論 1 5
  • 摘要 最近,公司業(yè)務(wù)上有個生成二維碼圖片的需求(Android端),之后筆者在網(wǎng)上查閱了一些資料,實(shí)現(xiàn)了這個功能。...
    夢想編織者灬小楠閱讀 46,297評論 37 132
  • 回不回家? 小李,單身,IT男,來自西部偏遠(yuǎn)城市A市,十年前大學(xué)剛畢業(yè),獨(dú)自踏上南下的列車,來到S市打拼...
    牧野俊風(fēng)閱讀 329評論 0 0

友情鏈接更多精彩內(nèi)容