美文网首页框架【库】
android 扫一扫集成

android 扫一扫集成

作者: 30cf443c3643 | 来源:发表于2018-09-05 09:06 被阅读232次

    扫一扫的功能比较常见,其中最常用的是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<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();
      }
    

    先开启一个解码线程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 扫一扫集成

      本文链接:https://www.haomeiwen.com/subject/fshgwftx.html