美文网首页项目Android进阶之路
Android扫描二维码SimpleZxing源码解析

Android扫描二维码SimpleZxing源码解析

作者: gustiness | 来源:发表于2017-09-11 22:21 被阅读95次

源码地址

谷歌官方有一个关于二维码的zxing项目,地址为zxing,但是这个库对于安卓应用来说太大了。有一个开发者将这个库进行了简化,地址为SimpleZxing,使得可以非常方便地使用在安卓工程中。

源码解析

我们可以通过以下代码来跳转到扫描二维码界面:

    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);
    }

很容易就能看出,这里仅仅是传递了一些参数,跳转到了一个新的activity,那么我们进入到这个新的activity中去查看。由于onCreate中没有重要的代码,所以我们查看onResume方法。

@Override
    protected void onResume() {
        super.onResume();
        if (orientationMode == VALUE_ORIENTATION_AUTO) {
            myOrientationDetector.enable();
        }
        cameraManager = new CameraManager(getApplication(), needExposure, needFullScreen);
        //viewfinderView实际上就是我们需要绘出的扫描二维码的框,以及正在扫描的线
        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);
        }
        //用来实时显示相机的图像
        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);
        }

    }

与之对应的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" />

看到这里,我想大家应该明白了。用SurfaceView来显示相机的图像,然后用viewfinderView来画出一个蓝色的扫描矩形、扫描射线等。这样就能够对相机进行定制,接下来,我们来看一下其中的细节吧~

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

private void initCamera(SurfaceHolder surfaceHolder) {
        if (surfaceHolder == null) {      //如果用来显示相机图像的容器为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, 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) {
            //使用系统api,打开相机
            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) {
            //设置扫描二维码的区域位置和大小
            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);

    }

这里我们就知道了如何将相机获取的画面展示在容器中,那么现在最重要的问题也就是:怎么样才能够实时地获取照片,并且检测照片中是否存在二维码呢?让我们回到initCamera方法中找答案,可以看到另一个重要的代码:handler = new CaptureActivityHandler(this, cameraManager); 跟进去构造函数:

CaptureActivityHandler(CaptureActivity activity, CameraManager cameraManager) {
        this.activity = activity;
        //创建并开启一个专门用来检测二维码的线程,防止主线程卡顿
        decodeThread = new DecodeThread(activity, new ViewfinderResultPointCallback(activity.getViewfinderView()));
        decodeThread.start();
        state = State.SUCCESS;
        // Start ourselves capturing previews and decoding.
        this.cameraManager = cameraManager;
        //在startPreview方法执行之后,SurfaceView才真的开始显示照相机内容
        cameraManager.startPreview();
        //重要方法
        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);
        }
    }

可以看出来,setOneShotPreviewCallback方法将照相机的某一时刻的截屏发送给DecodeHandler,所以接下来的逻辑应该在它的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();

        }
    }

正常情况下,逻辑应该跳转到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();
            }
        }
    }

无论这个图片中是否被扫描到了二维码,都会转移到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;
            //当没有检索到二维码时,重新进行刚才我们所分析的流程
            cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);

        }
    }

    public void handleDecode(Result rawResult) {
        //播放扫描成功的声音
        beepManager.playBeepSoundAndVibrate();
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //将扫描结果返回到上一个页面
        returnResult(RESULT_OK, rawResult.getText());
    }

哈,这样就把两个最重要的部分分析完了!另一个我想提的是,这个代码中有自动对焦的功能,它的实现方法为:

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) {
                    //实现自动对焦
                    autoFocusManager = new AutoFocusManager(theCamera.getCamera());
                    autoFocusManager.start();
                }
            }
        }
    }

希望大家有所收获~

相关文章

网友评论

    本文标题:Android扫描二维码SimpleZxing源码解析

    本文链接:https://www.haomeiwen.com/subject/swtqsxtx.html