android 扫一扫集成

扫一扫的功能比较常见,其中最常用的是zxing和zbar。zxing项目是谷歌推出的用来识别多种格式条形码的开源项目。ZBar基于C语言编写,解码效率高于Zxing项目。
Zxing的github地址
Zbar的官网

集成Zxing

下载

下载jar包: http://repo1.maven.org/maven2/com/google/zxing/core/3.3.3/
下载Zxing库

2018-09-04_105652.png

接入项目

  1. 在你项目中,File -> New -> Import Module 把刚下载的android包添加进入
  2. 然后在ZXing的build.gradle下第一行改成如下,还有把下面的 applicationId那行删掉。
修改前
apply plugin: 'com.android.application'

修改后
apply plugin: 'com.android.library'
  1. 修改清单文件。运行时就会报Execution failed for task ‘app:processDebugManifest’,只要自己项目的AndroidManifest.xml文件 application标签加上 tools:replace=”icon,theme”。同时module的清单文件里会有CaptureActivity的默认启动项,去除掉
  2. 把Core Jar包导入ZXing,在ZXing创建一个libs文件夹,把Core Jar放进去,然后右键 As Add Library
  3. 修改错误。build后会报错:Resource IDs cannot be used in a switch statement in Android library modules。在android项目的library module里不能使用资源ID作为switch语句的case值。为什么呢?因为switch里的case值必须是常数,而在library module的R文件里ID的值不是final类型的,但是主module的R文件里的ID值是final类型的,所以主module里可以用资源ID作为case值而library module却不能。那问题怎么解决呢?把switch-case转成if-else呗。接下来发现会少一个CameraConfigurationUtils类,这个就是刚才在android-core下的那一个类,把它拖到camera包下就好了。

运行

直接Intent启动CaptureActivity(可能需要先去修改相机权限),得到大概如下的页面

2018-09-04_105703.png

google的原项目集成了很多的功能。所以还要对它进行精简

其他开源库

https://github.com/journeyapps/zxing-android-embedded

代码分析

简化后 最主要的几个文件

2018-09-04_105716.png

最主要的步骤如图

2018-09-04_105725.png

首先CaptureActivity 是主要的扫码界面,在onResume里面 1.初始化了CameraManager 初始化ViewfinderView 初始化SurfaceView

 @Override
    protected void onResume() {
        super.onResume();
        cameraManager = new CameraManager(getApplication()); //1

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

        SurfaceView surfaceView = (SurfaceView) findViewById(R.id.scanner_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);
        }

    }

2.当SurfaceView creat成功后会去initCamera

@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);
    }
  }

3.在initCamera时,他又会创建了一个CaptureActivityHandler

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);//先进行openDriver打开摄像头
            // Creating the handler starts the preview, which can also throw a RuntimeException.
            if (handler == null) {
                handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);
            }
        } catch (IOException ioe) {
            Log.w(TAG, ioe);
        } 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);
        }
    }

4.看一下CaptureActivityHandler的构造方法,这个类继承Handler,用来处理各种扫描解析的消息,在构造函数里就开始了扫描的过程

  public CaptureActivityHandler(CaptureActivity activity,
                         Collection decodeFormats,
                         Map 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();
  }

先开启一个解码线程DecodeThread,然后cameraManager.startPreview()实际是里面是调用了系统Camera类的startPreview()方法

public synchronized void startPreview() {
    OpenCamera theCamera = camera;
    if (theCamera != null && !previewing) {
      theCamera.getCamera().startPreview();
      ...
    }
  }

restartPreviewAndDecode方法就是不断发送解析消息给decodeThread进行解析

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

/**
   * 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);
    }
  }

demo的注释写得蛮清楚,将我们刚刚传入的handler设置给摄像头数据回调previewCallback,数据会以byte数组的方式返回
PreviewCallback,并实现了Camera.PreviewCallback的onPreviewFrame接口

final class PreviewCallback implements Camera.PreviewCallback {

  private static final String TAG = PreviewCallback.class.getSimpleName();

  private final CameraConfigurationManager configManager;
  private Handler previewHandler;
  private int previewMessage;

  PreviewCallback(CameraConfigurationManager configManager) {
    this.configManager = configManager;
  }

  void setHandler(Handler previewHandler, int previewMessage) {
    this.previewHandler = previewHandler;
    this.previewMessage = previewMessage;
  }

  @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");
    }
  }

}

这个方法里面的previewHandler和previewMessage,就是我们在requestPreviewFrame中setHandler方法传入的两个参数,可以看到在onPreviewFrame中会将摄像头获取的数据,还有摄像头视觉的宽高封装到Message中发送给刚刚的DecodeThread去解析.
DecodeThread继承自Thread,在该Thread的run函数中会新见一个消息队列,并用于解析条形码
其run函数如下

 @Override
  public void run() {
    Looper.prepare();
    handler = new DecodeHandler(activity, hints);//在该子线程中新建一个消息队列,以接收数据并解析条形码的信息
    handlerInitLatch.countDown();
    Looper.loop();
  }

所以我们的解析过程就是在这个DecodeHandler里

  @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;
    }
  }

还有重要的一个过程即为设置需要解码的格式都哪些,Demo中有两种设置,在DecodeThread的构造函数中可以看出(第二个参数Collection< BarcodeFormat > decodeFormats),最终支持哪些解码格式由此处决定。在枚举类BarcodeFormat中已经定义17种码类型,需要支持二维码

decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);

其他类

  • BeepManager 扫描成功后的手机震动和提示音管理器
  • InactivityTimer 一段时间不操作(5分钟 ),关闭该应用(工具类)
  • ViewfinderView SurferView相机预览界面(取景器矩形和部分透明,激光扫描仪点动画和结果)
  • ViewfinderResultPointCallback SurferView相机预览回调
  • CameraConfigurationManager 设置相机的参数信息类

主要方法:

  1. initFromCameraParameters(OpenCamera camera) 计算了屏幕分辨率和当前最适合的相机像素
  2. setDesiredCameraParameters(OpenCamera camera, boolean safeMode) 读取配置设置相机的对焦模式、闪光灯模式等等
  3. getPreviewSizeOnScreen()
  4. getCameraResolution() //相机分辨率
  5. getScreenResolution() //屏幕分辨率
  6. getCWNeededRotation()
  7. getTorchState(Camera camera)
  8. setTorch(Camera camera, boolean newSetting)
  • CameraManager 摄像头管理类(打开,关闭)
  1. 闪光灯开关,CameraManager.setTorch(true),方法父类是CameraConfigurationManager ,闪光灯开启同时启动自动对焦
  2. 打开摄像头驱动 CameraManager.openDriver(SurfaceHolder)
  3. 设置扫码框显示位置 getFramingRectInPreview()
  4. 设置扫码框矩形的大小(根据屏幕分辨率) getFramingRect(),
  5. 设置扫码框矩形的大小(自定义 setManualFramingRect(int width, int height))
  • CameraFacing 打开前置或后置摄像头 枚举(BACK,FRONT)
  • OpenCamera bean类(获取摄像头部分参数index,camera,facing,orientation )
  • OpenCameraInterface 打开摄像头的接口类

参考ZXing 源码分析(简阅)

修改扫码框大小

默认的扫描界面太丑了,是长方形的,而且中间一根红线也不动,就是附近有几个点在闪烁。改聚焦框的大小,代码在CameraManager中,修改getFramingRect里的width height

自定义扫码样式

也就是修改ViewfinderView代码
参考示例:
Android-自定义View实现二维码网格扫描+纵向雷达的扫描效果

更新

今日头条的二维码扫描优化,等它开源

你可能感兴趣的:(android 扫一扫集成)