ZXing源码解析二:掌握解码步骤

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

内容简介:前言:上篇文章已经让源码运行起来了,但是还存在很多与扫描二维码无关的代码,本篇将删除无用的代码只保留与扫码有关的代码,同时分析解码的步骤。本篇文章的目标是分析出解码的步骤,为了不被无关的代码干扰,将会对源码进行精简,只保留与解码有关的代码。主要删减的代码就是识别出二维码的内容后,一些其他的操作,如分享,记录扫描的历史,搜索解析结果等。删除之后的

前言:上篇文章已经让源码运行起来了,但是还存在很多与扫描二维码无关的代码,本篇将删除无用的代码只保留与扫码有关的代码,同时分析解码的步骤。

精简代码

本篇文章的目标是分析出解码的步骤,为了不被无关的代码干扰,将会对源码进行精简,只保留与解码有关的代码。

主要删减的代码就是识别出二维码的内容后,一些其他的操作,如分享,记录扫描的历史,搜索解析结果等。删除之后的 android 模块的结构如下

ZXing源码解析二:掌握解码步骤

当然,这不是最终删减的版本,可能在分析源码的时候,发现无用的代码,会继续删除,最终的代码,我会在文末给出Github链接。

源码分析

为了方便理解及记忆 ZXing 解码的步骤,我会边分析边画UML的序列图,最后,分析完解码的步骤,会有一个完整的序列图。现在,从主程序的入口开始分析,就是 CaptureActivityonCreate 方法。

onCreate源码分析

代码如下

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;
    //控制activity在一段时间无操作自动finish
    inactivityTimer = new InactivityTimer(this);
    //管理扫码后是否有声音和震动
    beepManager = new BeepManager(this);
    //用来根据环境的明暗,自动开启关闭闪光灯
    ambientLightManager = new AmbientLightManager(this);
    //加载一些默认的配置
    PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
  }
复制代码

这个方法,主要是用来实例化一些对象和获取配置信息,上面的代码中已经有注释,就不再细说。

onResume源码分析

下面继续看 Activity 生命周期的第二个方法,代码如下

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());//1

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

    resultView = findViewById(R.id.result_view);
    statusView = (TextView) findViewById(R.id.status_view);
    handler = null;
    //省略不重要代码
    //.....
    SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
    SurfaceHolder surfaceHolder = surfaceView.getHolder();//2
    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);//3
    }
  }
复制代码

上面代码中的一些语句标记了序号,现在来看序号“1”处的代码都做了什么,进入 CameraManager 类的构造方法中,代码如下

public CameraManager(Context context) {
    this.context = context;
    this.configManager = new CameraConfigurationManager(context);//1.1
    previewCallback = new PreviewCallback(configManager);//1.2
  }
复制代码

继续跟进代码,看下“1.1”处的代码, CameraConfigurationManager 构造方法中做了什么,代码如下

CameraConfigurationManager(Context context) {
    this.context = context;
  }
复制代码

上面的代码就是注入了 context 。现在看“1.2”处的代码, PreviewCallback 构造方法中做了什么,代码如下

PreviewCallback(CameraConfigurationManager configManager) {
    this.configManager = configManager;
  }
复制代码

上面的这段代码可以看出,在 PreviewCallback 构造方法中,将 CameraConfigurationManager 类的实例,注入到了 PreviewCallback 类中。跟完了“1”处的代码,继续往下看 onResume 方法中的代码,这里介绍一下“2”处的代码, SurfaceHolder 的作用,介绍如下

SurfaceHolder是一个接口,其作用就像一个Surface的监听器。提供访问和控制SurfaceView背后的Surface 相关的方法 (providingaccess and control over this SurfaceView's underlying surface),它通过三个回调方法,让我们可以感知到Surface的创建、销毁或者改变。

继续往下看代码,因为在 onCreate 方法中, hasSurface 值为 false ,所以,会进入 else 语句,也就是“3”处的代码,这句代码的作用就是绑定 Surface 的监听器,就是在当前的 Activity 中绑定 Surface 生命周期的回调方法。 SurfaceHolder.Callback 中定义了三个接口方法:

  • public void surfaceChanged(SurfaceHolder holder, int format, int width, int height); 当surface发生任何结构性的变化时(格式或者大小),该方法就会被立即调用。

  • public void surfaceCreated(SurfaceHolder holder); 当surface对象创建后,该方法就会被立即调用。

  • public void surfaceDestroyed(SurfaceHolder holder); 当surface对象在将要销毁前,该方法会被立即调用。

知道了这三个方法在什么时候会调用,所以,这里绑定回调之后,会首先调用 surfaceCreated 这个回调方法,看下这个方法中的代码,如下

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

继续跟进代码,看下 initCamera(holder); 方法都做了什么,代码如下

private void initCamera(SurfaceHolder surfaceHolder) {
    if (surfaceHolder == 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);
      // 实例化一个handler并开始预览.
      if (handler == null) {
        //3.1
        handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);
      }
      decodeOrStoreSavedBitmap(null, null);
    } catch (IOException ioe) {
      Log.w(TAG, ioe);
      displayFrameworkBugMessageAndExit();
    } catch (RuntimeException e) {
      // Barcode Scanner has seen crashes in the wild of this variety:
      // java.?lang.?RuntimeException: Fail to connect to camera service
      Log.w(TAG, "Unexpected error initializing camera", e);
      displayFrameworkBugMessageAndExit();
    }
  }
复制代码

“3.1”处的代码实例化了一个 CaptureActivityHandler ,看下 CaptureActivityHandler 的构造方法,代码如下

CaptureActivityHandler(CaptureActivity activity,
                         Collection<BarcodeFormat> decodeFormats,
                         Map<DecodeHintType,?> baseHints,
                         String characterSet,
                         CameraManager cameraManager) {
    this.activity = activity;//注入activity
    //新建一个线程并启动
    //3.1.1
    decodeThread = new DecodeThread(activity, decodeFormats, baseHints, characterSet,
        new ViewfinderResultPointCallback(activity.getViewfinderView()));
    decodeThread.start();
    state = State.SUCCESS;

    // 注入cameraManager
    this.cameraManager = cameraManager;
    //要求相机硬件开始将预览帧绘制到屏幕上
    cameraManager.startPreview();
    //开始预览,并且解码
    //3.1.2
    restartPreviewAndDecode();
  }
复制代码

现在来看“3.1.1”处新建线程都做了什么, DecodeThread 构造方法的代码如下

DecodeThread(CaptureActivity activity,
               Collection<BarcodeFormat> decodeFormats,
               Map<DecodeHintType,?> baseHints,
               String characterSet,
               ResultPointCallback resultPointCallback) {

    this.activity = activity;
    handlerInitLatch = new CountDownLatch(1);

    hints = new EnumMap<>(DecodeHintType.class);
    if (baseHints != null) {
      hints.putAll(baseHints);
    }

    // The prefs can't change while the thread is running, so pick them up once here.
    if (decodeFormats == null || decodeFormats.isEmpty()) {
      SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
      decodeFormats = EnumSet.noneOf(BarcodeFormat.class);
      if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_1D_PRODUCT, true)) {
        decodeFormats.addAll(DecodeFormatManager.PRODUCT_FORMATS);
      }
      if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_1D_INDUSTRIAL, true)) {
        decodeFormats.addAll(DecodeFormatManager.INDUSTRIAL_FORMATS);
      }
      if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_QR, true)) {
        decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
      }
      if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_DATA_MATRIX, true)) {
        decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
      }
      if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_AZTEC, false)) {
        decodeFormats.addAll(DecodeFormatManager.AZTEC_FORMATS);
      }
      if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_PDF417, false)) {
        decodeFormats.addAll(DecodeFormatManager.PDF417_FORMATS);
      }
    }
    hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);

    if (characterSet != null) {
      hints.put(DecodeHintType.CHARACTER_SET, characterSet);
    }
    hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
  }
复制代码

上面的代码可以发现,在线程的构造方法中主要是设置解码的格式。

如果想提升扫码速度,这里是一个可以优化的点,可以不用设置这么多格式,只设置与自己业务有关的解码格式。

都知道线程运行,会调用 run 方法,看下 run 方法中的代码,如下

public void run() {
    Looper.prepare();
    handler = new DecodeHandler(activity, hints);
    handlerInitLatch.countDown();
    Looper.loop();
  }
复制代码

这段代码的作用是在子线程中实例化了一个 Handler 与当前线程绑定。继续跟进代码,看下 DecodeHandler 的构造方法都做了什么,代码如下

DecodeHandler(CaptureActivity activity, Map<DecodeHintType,Object> hints) {
    multiFormatReader = new MultiFormatReader();
    multiFormatReader.setHints(hints);
    this.activity = activity;
  }
复制代码

这段代码的作用就是将线程构造方法中设置的 hints 设置给实例化的 MultiFormatReader ,同时注入 CaptureActivity 的实例。

MultiFormatReader 类的作用是一个便利类,是大多数用途的库的主要入口点。

分析到这里,可以画出如下的序列图

ZXing源码解析二:掌握解码步骤

接着来分析“3.1.2”处的代码,调用的方法代码如下

private void restartPreviewAndDecode() {
    if (state == State.SUCCESS) {
      state = State.PREVIEW;
      cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
      activity.drawViewfinder();
    }
  }
复制代码

重点看下 cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode); 这句代码,看下 CameraManager 中的 requestPreviewFrame 方法做了什么,代码如下

/**
   * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
   * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
   * respectively.
   *
   * @param handler The handler to send the message to.
   * @param message The what field of the message to be sent.
   */
  public synchronized void requestPreviewFrame(Handler handler, int message) {
    OpenCamera theCamera = camera;
    if (theCamera != null && previewing) {
      previewCallback.setHandler(handler, message);
      theCamera.getCamera().setOneShotPreviewCallback(previewCallback);
    }
  }
复制代码

看下这个方法的介绍,意思是解析一个预览帧,解析的数据是一个字节数组,放进了 message.obj 中,宽和高放到了 message.arg1message.arg2 中,然后将 message 返回给传进来的 handler ,由前文可只,这个 handlerDecodeHandler 的实例。 好了,跟到这个方法,就不继续网下跟了,这里可以猜测一下,一帧图像解析后会回调 PreviewCallback 类中的 onPreviewFrame 方法,这个方法的代码如下

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

不难看出,这里是把解析后的数据发送给了 DecodeHandler ,最终会调用 DecodeHandler 类中的 handleMessage 方法,代码如下

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

而上面代码中的 message.what 的值刚好是 R.id.decode ,自然就进入了 decode 方法。 分析到这里,在来看下现在的时序图,如下

ZXing源码解析二:掌握解码步骤

根据这个序列图和前文,可以知道以下内容

  • 进入扫码界面会先实例化 CameraManagerPreviewCallback 类。
  • surface 回调方法中,初始化相机设置相机的配置参数。
  • 新建一个 DecodeThread 线程并启动。为此线程绑定一个 DecodeHandler
  • 获取相机帧数据转换程 byte 数组传回 DecodeHandler 进行解码。

上文已经完成相机获取图像到进行解码的源码分析,从前面的分析可以知道,解码的方法是在子线程中执行的,那么子线程解码成功,怎么通知主线程能,其实非常简单,可以从 DecodeHandler 中的 decode 方法中知道答案, decode 方法的代码如下

private void decode(byte[] data, int width, int height) {
    long start = System.nanoTime();
    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();
      }
    }
    //获取了CaptureActivity中的handler
    Handler handler = activity.getHandler();
    if (rawResult != null) {
      // Don't log the barcode contents for security.
      long end = System.nanoTime();
      Log.d(TAG, "Found barcode in " + TimeUnit.NANOSECONDS.toMillis(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发送给CaptureActivity中的handler
        message.sendToTarget();
      }
    } else {
      if (handler != null) {
        Message message = Message.obtain(handler, R.id.decode_failed);
        message.sendToTarget();
      }
    }
  }
复制代码

从上面的代码中可以发现将解码的结果发送给主线程是利用Android的Handler机制。

结束语

因为本文的目标是掌握解码的步骤,所以一些细节性的代码并没有进行分析,如配置相机的参数,扫码后的图像是竖屏还是横屏,怎样获取最佳的图像数据进行解析等。细节性的东西将会放到后面的文章进行讲解,后面的文章还会分析具体是怎么获取图像上的二维码并进行解码的。

本文已由公众号“AndroidShared”首发

ZXing源码解析二:掌握解码步骤
扫码关注公众号,回复“获取资料”有惊喜

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

查看所有标签

猜你喜欢:

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

Beginning Google Maps API 3

Beginning Google Maps API 3

Gabriel Svennerberg / Apress / 2010-07-27 / $39.99

This book is about the next generation of the Google Maps API. It will provide the reader with the skills and knowledge necessary to incorporate Google Maps v3 on web pages in both desktop and mobile ......一起来看看 《Beginning Google Maps API 3》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具