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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
云解析DNS,个人版 1个月
简介: ZXing源码解析二:掌握解码步骤

接上一篇

来看“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类的作用是一个便利类,是大多数用途的库的主要入口点。

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

接着来分析“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方法。 分析到这里,在来看下现在的时序图,如下

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

  • 进入扫码界面会先实例化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机制。

结束语

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

  点击这里获取源码


相关文章
|
16天前
|
机器学习/深度学习 缓存 算法
netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk
netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk
|
3天前
|
存储 安全 Java
深度长文解析SpringWebFlux响应式框架15个核心组件源码
以上是Spring WebFlux 框架核心组件的全部介绍了,希望可以帮助你全面深入的理解 WebFlux的原理,关注【威哥爱编程】,主页里可查看V哥每天更新的原创技术内容,让我们一起成长。
|
4天前
|
关系型数据库 分布式数据库 数据库
PolarDB-X源码解析:揭秘分布式事务处理
【7月更文挑战第3天】**PolarDB-X源码解析:揭秘分布式事务处理** PolarDB-X,应对大规模分布式事务挑战,基于2PC协议确保ACID特性。通过预提交和提交阶段保证原子性与一致性,使用一致性快照隔离和乐观锁减少冲突,结合故障恢复机制确保高可用。源码中的事务管理逻辑展现了优化的分布式事务处理流程,为开发者提供了洞察分布式数据库核心技术的窗口。随着开源社区的发展,更多创新实践将促进数据库技术进步。
11 3
|
10天前
|
NoSQL Java Redis
【源码解析】自动配置的这些细节都不知道,别说你会 springboot
【源码解析】自动配置的这些细节都不知道,别说你会 springboot
|
4天前
|
前端开发 开发者
深入解析Vite.js源码
【7月更文挑战第1天】Vite.js 深入解析:以其无bundle开发、动态ES模块加载提升开发效率;本地HTTP服务器配合WebSocket实现热更新;按需加载减少资源占用;预构建优化生产环境性能;基于Rollup的插件系统增强灵活性。Vite,一个创新且高效的前端构建工具。
14 0
|
10天前
|
Java 容器 Spring
Spring5源码解析5-ConfigurationClassPostProcessor (上)
Spring5源码解析5-ConfigurationClassPostProcessor (上)
|
16天前
|
XML Java 数据格式
Spring容器启动源码解析
Spring容器启动源码解析
|
18天前
|
XML Java 数据格式
深度解析 Spring 源码:从 BeanDefinition 源码探索 Bean 的本质
深度解析 Spring 源码:从 BeanDefinition 源码探索 Bean 的本质
25 3
|
17天前
|
存储 NoSQL 算法
Redis(四):del/unlink 命令源码解析
Redis(四):del/unlink 命令源码解析
|
18天前
|
XML Java 数据格式
深度解析 Spring 源码:揭秘 BeanFactory 之谜
深度解析 Spring 源码:揭秘 BeanFactory 之谜
20 1

推荐镜像

更多