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<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实现二维码网格扫描+纵向雷达的扫描效果

更新

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容