美文网首页项目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