zxing开源库工作流程源码详解

栏目: IOS · Android · 发布时间: 6年前

内容简介:作为移动客户端开发者来说,对二维码识别或二维码生成相关的开发需求肯定并不陌生,Android开发二维码相关的功能通常都会使用或参考大名鼎鼎的首先使用git将项目代码clone到本地,新建项目,将zxing文件夹中的Demo代码运行起来后,会进入一个扫描的主功能界面,将扫描框对准一个二维码即可弹出解析结果信息的浮框。通过

作为移动客户端开发者来说,对二维码识别或二维码生成相关的开发需求肯定并不陌生,Android开发二维码相关的功能通常都会使用或参考大名鼎鼎的 zxing 库。而本文则主要是通过源码分析一下该开源库扫描二维码的工作流程,对这块能有个更深的了解。

首先使用git将项目代码clone到本地,新建项目,将zxing文件夹中的 android 以及 core 文件夹代码覆盖到对应的目录下,稍作一些修改即可运行一个简单的二维码扫描的示例应用。

整体流程

Demo代码运行起来后,会进入一个扫描的主功能界面,将扫描框对准一个二维码即可弹出解析结果信息的浮框。通过 AndroidManifest.xml 文件中可以得知这个页面对应的类为 CaptureActivity.java ,我们便从这个类开始,分析整个二维码扫描的流程。

要分析一个Activity,当然要从它的生命周期所对应的各个方法说起。首先我们来看它的 onCreate() 方法:

@Override
public void onCreate(Bundle icicle) {
    super.onCreate(icicle);

    //保持屏幕常亮
    Window window = getWindow();
    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    setContentView(R.layout.capture);

    hasSurface = false;
    inactivityTimer = new InactivityTimer(this);
    beepManager = new BeepManager(this);
    ambientLightManager = new AmbientLightManager(this);

    PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
}
复制代码

这个方法代码不多,也很容易看懂,主要就是做一些初始化的工作。 InactivityTimer 主要是用来监听当手机是使用电池而不是充电状态时,如果5分钟内没有做任何操作,则主动finish掉activity。 BeepManager 负责扫描到结果后震动或铃声相关, AmbientLightManager 则是负责控制闪光灯。

继续往下走看 onResume() 方法:

@Override
protected void onResume() {
    super.onResume();

    ...

    // CameraManager must be initialized here, not in onCreate(). This is necessary because we don't
	// want to open the camera driver and measure the screen size if we're going to show the help on
	// first launch. That led to bugs where the scanning rectangle was the wrong size and partially
	// off screen.
	cameraManager = new CameraManager(getApplication());

    viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
    viewfinderView.setCameraManager(cameraManager);

    ...

    SurfaceView 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);
    }
}
复制代码

这个方法很重要,初始化了 CameraManager ,扫描二维码毋庸置疑是需要用到相机,通过相机预览的一帧一帧的图片,去解析上面可能存在的二维码信息。而在最后面还初始化了 SurfaceView ,通过 hasSurface 来决定是走 initCamera(surfaceHolder) 还是 surfaceHolder.addCallback(this) 。在上面的 onCreate() 中我们可以看到 hasSurface 被初始化成 false ,所以这里走的应该是 else 的代码块。 CaptureActivity 实现了 SurfaceHolder.Callback 接口,因此该方法绑定了 surfaceHolder 的回调。当 SurfaceView 添加到 activity 中时,会调用 surfaceCreated()

@Override
public void surfaceCreated(SurfaceHolder holder) {
    if (holder == null) {
        Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!");
    }
    if (!hasSurface) {
        hasSurface = true;
        initCamera(holder);
    }
}
复制代码

这里我们看到会改变 hasSurface 的状态,然后走 initCamera(holder) ,和 onResume()hasSurfacetrue 时做的操作是一样的:

cameraManager.openDriver(surfaceHolder);
// Creating the handler starts the preview, which can also throw a RuntimeException.
if (handler == null) {
    handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);
}
复制代码

cameraManager 打开了驱动,并且把自己传入一个 CaptureActivityHandler 对象中去,那这个 CaptureActivityHandler 看起来像是一个进行消息通知的 Handler,它的具体作用又是什么呢?我们来看看它的构造方法:

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;

    // Start ourselves capturing previews and decoding.
    this.cameraManager = cameraManager;
    cameraManager.startPreview();
    restartPreviewAndDecode();
}
复制代码

通过进入 CaptureActivityHandler.java 可以看到该类确实继承了 Handler ,并且在它的构造方法中开启了一个 DecodeThread 的线程,并且调用了 cameraManagerstartPreview() 方法:

Asks the camera hardware to begin drawing preview frames to the screen.

开启相机预览后,再看下面的 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);
    }
}
复制代码

可以看到这个 handler 会一直传递到一个 previewCallback 对象中去,而 PreviewCallbacksetOneShotPreviewCallback() 方法的一个回调, setOneShotPreviewCallback 方法上的注释说明:

Installs a callback to be invoked for the next preview frame in addition to displaying it on the screen. After one invocation, the callback is cleared. This method can be called any time, even when preview is live. Any other preview callbacks are overridden.

使用此方法注册预览回调接口时,会将下一帧数据回调给 onPreviewFrame() 方法,调用完成后这个回调接口将被销毁,也就是只会回调一次预览帧数据。继续顺着这个方法走下去,看回调方法 onPreviewFrame()

@Override
public void onPreviewFrame(byte[] data, Camera camera) {
    Point cameraResolution = configManager.getCameraResolution();
    Handler thePreviewHandler = previewHandler;
    if (cameraResolution != null && thePreviewHandler != null) {
        Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,cameraResolution.y, data);
        message.sendToTarget();
        previewHandler = null;
    } else {
        Log.d(TAG, "Got preview callback, but no handler or resolution available");
    }
}
复制代码

这里将返回的 byte 数组数据和预览帧的宽高信息通过 handler 进行通知,这个 handler 就是上文中传过来的 decodeThread.getHandler()previewMessageR.id.decode ,目的就是把图片数据拿到该线程中进行解析。我们跟进到 DecodeHandler.java 中查看 handleMessage() 方法:

@Override
public void handleMessage(Message message) {
    if (message == null || !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;
    }
}

private void decode(byte[] data, int width, int height) {
    long start = System.currentTimeMillis();
  
    //省略具体解析代码
    ...
    Handler handler = activity.getHandler();//CaptureActivityHandler
    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();
        }
    }
}
复制代码

上面代码很清晰, DecodeHandler 接收到 R.id.decode 的消息后,会调用 decode() 方法去解析传过来的图片数据。经过一系列解析操作,得到结果。如果结果为不为空,则通过 CaptureActivityHandler 将解析成功的消息传到 CaptureActivity 中进行后续解析结果展示。而如果解析结果为空呢,说明二维码信息解析失败了,传了一个 R.id.decode_failedCaptureActivityHandler 中:

@Override
public void handleMessage(Message message) {
    switch (message.what) {

    	...

        case 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);
            break;
            
        ...
    }
}

复制代码

可以看到,解析失败时,重新调用 requestPreviewFrame 获取下一帧预览照片,再拿去解析,知道返回正确结果或者手动退出。

整个过程的时序图如下:

zxing开源库工作流程源码详解

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C++ How to Program (5th Edition) (How to Program)

C++ How to Program (5th Edition) (How to Program)

Harvey & Paul) Deitel & Associates / Prentice Hall / 2005-01-05 / USD 98.00

With over 250,000 sold, Harvey and Paul Deitel's C++ How to Program is the world's best-selling introduction to C++ programming. Now, this classic has been thoroughly updated! The Deitels' groundbreak......一起来看看 《C++ How to Program (5th Edition) (How to Program)》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

URL 编码/解码
URL 编码/解码

URL 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换