Android扫描二维码SimpleZxing源码解析

源码地址

谷歌官方有一个关于二维码的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();
                }
            }
        }
    }

希望大家有所收获~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容