Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 303|回复: 0

[默认分类] 条码扫描二维码扫描——ZXing android 源码简化

[复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2018-5-20 15:27:22 | 显示全部楼层 |阅读模式
    前言
      最近公司的Android项目需要用到摄像头做条码或二维码的扫描,Google一下,发现一个以Apache License 2.0 开源的 ZXing项目。Zxing项目里的Android实现太过复杂多余东西太多,得对其进行简化。
    前提条件
      下载源代码:点击这里
      编译核心库:Zxing的主页上有介绍具体步骤,大家也可以参照这篇博文:android 条码识别软件开发全解析(续2详解绝杀!)
    导入项目
      打开Eclipse 导入 源码中的 Android 项目,然后右击项目 选择“Build path”——》"Add External ArcHives" 把核心库 core.jar文件加入到项目中。
    此时编译一下项目,会发现报错,“ Multiple substitutions specified in non-positional format; did you mean to add the formatted="false" attribute?”之类的。打开raw 下的Values 发现错误是在一个<String>上。这里把 “preferences_custom_product_search_summary” 里的  %s  %f  全部都改成  %1$s  %1$f(因为我们用不到多国语言,建议只保留默认的Value ,其他全部删除)。
      原因:由于新的SDK采用了新版本的aapt(Android项目编译器),这个版本的aapt编译起来会比老版本更加的严格,然后在Android最新的开发文档的描述String的部分,已经说明如何去设置 %s 等符号
      “If you need to format your strings using String.format(String, Object...) , then you can do so by putting your format arguments in the string resource. For example, with the following resource:
      <string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>
      In this example, the format string has two arguments: %1$s is a string and %2$d is a decimal number. You can format the string with arguements from your application...“
      经过以上步骤后项目应该就可以运行了。
      但是ZXing的android项目东西太多了,有很多是我们不需要的,得新建另一个项目简化它。
    简化
      在开始前大致介绍一下简化ZXing需要用到各个包 、类的职责。

    CaptureActivity。这个是启动Activity 也就是扫描器(如果是第一安装,它还会跳转到帮助界面)。
    CaptureActivityHandler 解码处理类,负责调用另外的线程进行解码。
    DecodeThread 解码的线程。
    com.google.zxing.client.android.camera 包,摄像头控制包。
    ViewfinderView 自定义的View,就是我们看见的拍摄时中间的框框了。

    新建另一个项目
      新建另一个项目将启动的Activity命名为CaptureActivity,并导入核心库。项目新建完成后我们打开 CaptureActivity 的布局文件,我这里为main。把里面的XML修改为:


       1
      <
      FrameLayout
      xmlns:android
      ="http://schemas.android.com/apk/res/android"
      

       2
       android:layout_width
      ="fill_parent"
       android:layout_height
      ="fill_parent"
      >
      

       3
      <
      SurfaceView
      android:id
      ="@+id/preview_view"
      

       4
       android:layout_width
      ="fill_parent"
       android:layout_height
      ="fill_parent"
      

       5
       android:layout_centerInParent
      ="true"
      />
      

       6
       

       7
      <
      com.Zxing.Demo.view.ViewfinderView

       8
      android:id
      ="@+id/viewfinder_view"
       android:layout_width
      ="fill_parent"
      

       9
       android:layout_height
      ="fill_parent"
       android:background
      ="@android:color/transparent"
      />
      

      10
      <
      TextView
      android:layout_width
      ="wrap_content"
      

      11
       android:id
      ="@+id/txtResult"
      

      12
       android:layout_height
      ="wrap_content"
       android:text
      ="@string/hello"
      />
      

      13
       

      14
       
      </
      FrameLayout
      >
      

      可以看到在XML里面用到了 ViewfinderView 自定义view 。所以新建一个View 的包,然后把:ViewfinderView 和 ViewfinderResultPointCallback 靠到里面(记得对应修改XML里面的包)。
    打开 CaptureActivity 覆盖 onCreate 方法:


       1
      @Override

       2
      public
      void
       onCreate(Bundle savedInstanceState) {

       3
      super
      .onCreate(savedInstanceState);

       4
       setContentView(R.layout.main);

       5
      //
      初始化 CameraManager
      

       6
       
       CameraManager.init(getApplication());

       7
      

       8
       viewfinderView
      =
       (ViewfinderView) findViewById(R.id.viewfinder_view);

       9
       txtResult
      =
       (TextView) findViewById(R.id.txtResult);

      10
       hasSurface
      =
      false
      ;

      11
       inactivityTimer
      =
      new
       InactivityTimer(
      this
      );

      12
       }
      

      这里调用到的 CameraManager 类是控制摄像头的包里的类。新建一个camera包把:com.google.zxing.client.android.camera 里面的类全部拷入,另外我把PlanarYUVLuminanceSource也拷入到这个包里面。根据错误的提示来修正代码,主要是修改正包结构。(整个简化的流程都是如此:“根据错误提示,修改代码”)。

      在修改的过程中,有很多是关于R 资源的问题,在此我们需要将Values  里面的两个xml资源文件拷入项目中:colos.xml 和ids.xml 。 ctrl+b 一下看看error 是不是少了很多。在CameraManager中有些地方需要用到项目的配置,这里需要把配置直接写入代码中:


      //
       SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);

      //
      是否使用前灯

      //
       if (prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false)) {

      //
       FlashlightManager.enableFlashlight();

      //
       }
      

       FlashlightManager.enableFlashlight();
      

      使用摄像头需要加入相应的权限:


      <
      uses
      -
      permission android:name
      =
      "
      android.permission.CAMERA
      "
      ></
      uses
      -
      permission
      >
      

      <
      uses
      -
      permission android:name
      =
      "
      android.permission.WRITE_EXTERNAL_STORAGE
      "
      ></
      uses
      -
      permission
      >
      

      <
      uses
      -
      feature android:name
      =
      "
      android.hardware.camera
      "
      />
      

      <
      uses
      -
      feature android:name
      =
      "
      android.hardware.camera.autofocus
      "
      />
      

      <
      uses
      -
      permission android:name
      =
      "
      android.permission.VIBRATE
      "
      />
      

      <
      uses
      -
      permission android:name
      =
      "
      android.permission.FLASHLIGHT
      "
      />
      

      当View 和 camera 包里的错误修正完成后,我们继续来看CaptureActivity。
    覆盖onResume方法初始化摄像头:


      @Override

      protected
      void
       onResume() {

      super
      .onResume();
    SurfaceView surfaceView
      =
       (SurfaceView) findViewById(R.id.preview_view);
    SurfaceHolder surfaceHolder
      =
       surfaceView.getHolder();

      if
       (hasSurface) {
    initCamera(surfaceHolder);
    }
      else
       {
    surfaceHolder.addCallback(
      this
      );
    surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
    decodeFormats
      =
      null
      ;
    characterSet
      =
      null
      ;

    playBeep
      =
      true
      ;
    AudioManager audioService
      =
       (AudioManager) getSystemService(AUDIO_SERVICE);

      if
       (audioService.getRingerMode()
      !=
       AudioManager.RINGER_MODE_NORMAL) {
    playBeep
      =
      false
      ;
    }
    initBeepSound();
    vibrate
      =
      true
      ;
    }
      




    initCamera
      
      
        1
       private
       void
        initCamera(SurfaceHolder surfaceHolder) {

        2
       try
        {

        3
        CameraManager.get().openDriver(surfaceHolder);

        4
        }
       catch
        (IOException ioe) {

        5
       return
       ;

        6
        }
       catch
        (RuntimeException e) {

        7
       return
       ;

        8
        }

        9
       if
        (handler
       ==
       null
       ) {

       10
        handler
       =
       new
        CaptureActivityHandler(
       this
       , decodeFormats,

       11
        characterSet);

       12
        }

       13
        }
       
      




    SurfaceHolder接口实现
      
      
        @Override

       public
       void
        surfaceChanged(SurfaceHolder holder,
       int
        format,
       int
        width,

       int
        height) {

    }

    @Override

       public
       void
        surfaceCreated(SurfaceHolder holder) {

       if
        (
       !
       hasSurface) {
    hasSurface
       =
       true
       ;
    initCamera(holder);
    }

    }

    @Override

       public
       void
        surfaceDestroyed(SurfaceHolder holder) {
    hasSurface
       =
       false
       ;

    }
       
      

    initCamera () 方法用于初始化摄像头,如果排除了所有的error ,运行项目时就可以看到大致扫描界面了。 surfaceHolder.addCallback(this);表示让CaptureActivity实现其callback接口。
    handler = new CaptureActivityHandler(this, decodeFormats,characterSet) 用于进行扫描解码处理。
    解码
      上面的步骤主要都是用于对摄像头的控制,而解码的真正工作入口是在CaptureActivityHandler 里面的。新建一个Decoding包把以下文件拷入包中:

    CaptureActivityHandler
    DecodeFormatManager
    DecodeHandler
    DecodeThread
    FinishListener
    InactivityTimer
    Intents

    由于我们的包结构和Zxing 项目的有所不同所以需要注意一下类的可访问性
    同样开始ctrl+B 编译一下,然后开始修正错误。
      在CaptureActivityHandler 里 把 handleMessage 里的部分方法先注释掉如:“decode_succeeded ”分支,这是解码成功时调用 CaptureActivity 展示解码的结果。
    在DecodeThread 类里,修改部分涉及Preference配置的代码:


      DecodeThread(CaptureActivity activity,
    Vector
      <
      BarcodeFormat
      >
       decodeFormats,
    String characterSet,
    ResultPointCallback resultPointCallback) {


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

    hints
      =
      new
       Hashtable
      <
      DecodeHintType, Object
      >
      (
      3
      );


      //
      //
       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 = new Vector<BarcodeFormat>();

      //
       if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_1D, true)) {

      //
       decodeFormats.addAll(DecodeFormatManager.ONE_D_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
       (decodeFormats
      ==
      null
      ||
       decodeFormats.isEmpty()) {
    decodeFormats
      =
      new
       Vector
      <
      BarcodeFormat
      >
      ();
    decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);
    decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
    decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);

    }

    hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);


      if
       (characterSet
      !=
      null
      ) {
    hints.put(DecodeHintType.CHARACTER_SET, characterSet);
    }

    hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
    }
      

    这里是设置 解码的类型,我们现在默认将所有类型都加入。
    错误类型基本上都是:包结构、PreferencesActivity 的配置 、类可访问性的问题。根据错误提示耐心把错误解决。
    返回解码结果
      还记得在 CaptureActivityHandler 的 messagehandler 里注销掉的Case分支吗?现在CaptureActivity 里实现它。


      public
      void
       handleDecode(Result obj, Bitmap barcode) {
    inactivityTimer.onActivity();
    viewfinderView.drawResultBitmap(barcode);
    playBeepSoundAndVibrate();
    txtResult.setText(obj.getBarcodeFormat().toString()
      +
      "
      :
      "
      

      +
       obj.getText());
    }
      

    最后
      ZXing的简化已基本完成,有几位是可以运行成功的?呵呵。
    下面是CaptureActivity的源码:



    CaputreActivity  
      
      
       public
       class
        CaptureActivity
       extends
        Activity
       implements
        Callback {


       private
        CaptureActivityHandler handler;

       private
        ViewfinderView viewfinderView;

       private
       boolean
        hasSurface;

       private
        Vector
       <
       BarcodeFormat
       >
        decodeFormats;

       private
        String characterSet;

       private
        TextView txtResult;

       private
        InactivityTimer inactivityTimer;

       private
        MediaPlayer mediaPlayer;

       private
       boolean
        playBeep;

       private
       static
       final
       float
        BEEP_VOLUME
       =
       0.10f
       ;

       private
       boolean
        vibrate;


       /**
        Called when the activity is first created.
       */
       
    @Override

       public
       void
        onCreate(Bundle savedInstanceState) {

       super
       .onCreate(savedInstanceState);
    setContentView(R.layout.main);

       //
       初始化 CameraManager
       

        CameraManager.init(getApplication());

    viewfinderView
       =
        (ViewfinderView) findViewById(R.id.viewfinder_view);
    txtResult
       =
        (TextView) findViewById(R.id.txtResult);
    hasSurface
       =
       false
       ;
    inactivityTimer
       =
       new
        InactivityTimer(
       this
       );
    }

    @Override

       protected
       void
        onResume() {

       super
       .onResume();
    SurfaceView surfaceView
       =
        (SurfaceView) findViewById(R.id.preview_view);
    SurfaceHolder surfaceHolder
       =
        surfaceView.getHolder();

       if
        (hasSurface) {
    initCamera(surfaceHolder);
    }
       else
        {
    surfaceHolder.addCallback(
       this
       );
    surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
    decodeFormats
       =
       null
       ;
    characterSet
       =
       null
       ;

    playBeep
       =
       true
       ;
    AudioManager audioService
       =
        (AudioManager) getSystemService(AUDIO_SERVICE);

       if
        (audioService.getRingerMode()
       !=
        AudioManager.RINGER_MODE_NORMAL) {
    playBeep
       =
       false
       ;
    }
    initBeepSound();
    vibrate
       =
       true
       ;
    }

    @Override

       protected
       void
        onPause() {

       super
       .onPause();

       if
        (handler
       !=
       null
       ) {
    handler.quitSynchronously();
    handler
       =
       null
       ;
    }
    CameraManager.get().closeDriver();
    }

    @Override

       protected
       void
        onDestroy() {
    inactivityTimer.shutdown();

       super
       .onDestroy();
    }


       private
       void
        initCamera(SurfaceHolder surfaceHolder) {

       try
        {
    CameraManager.get().openDriver(surfaceHolder);
    }
       catch
        (IOException ioe) {

       return
       ;
    }
       catch
        (RuntimeException e) {

       return
       ;
    }

       if
        (handler
       ==
       null
       ) {
    handler
       =
       new
        CaptureActivityHandler(
       this
       , decodeFormats,
    characterSet);
    }
    }

    @Override

       public
       void
        surfaceChanged(SurfaceHolder holder,
       int
        format,
       int
        width,

       int
        height) {

    }

    @Override

       public
       void
        surfaceCreated(SurfaceHolder holder) {

       if
        (
       !
       hasSurface) {
    hasSurface
       =
       true
       ;
    initCamera(holder);
    }

    }

    @Override

       public
       void
        surfaceDestroyed(SurfaceHolder holder) {
    hasSurface
       =
       false
       ;

    }


       public
        ViewfinderView getViewfinderView() {

       return
        viewfinderView;
    }


       public
        Handler getHandler() {

       return
        handler;
    }


       public
       void
        drawViewfinder() {
    viewfinderView.drawViewfinder();

    }


       public
       void
        handleDecode(Result obj, Bitmap barcode) {
    inactivityTimer.onActivity();
    viewfinderView.drawResultBitmap(barcode);
    playBeepSoundAndVibrate();
    txtResult.setText(obj.getBarcodeFormat().toString()
       +
       "
       :
       "
       

       +
        obj.getText());
    }


       private
       void
        initBeepSound() {

       if
        (playBeep
       &&
        mediaPlayer
       ==
       null
       ) {

       //
        The volume on STREAM_SYSTEM is not adjustable, and users found it

       //
        too loud,

       //
        so we now play on the music stream.
       

        setVolumeControlStream(AudioManager.STREAM_MUSIC);
    mediaPlayer
       =
       new
        MediaPlayer();
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setOnCompletionListener(beepListener);

    AssetFileDescriptor file
       =
        getResources().openRawResourceFd(
    R.raw.beep);

       try
        {
    mediaPlayer.setDataSource(file.getFileDescriptor(),
    file.getStartOffset(), file.getLength());
    file.close();
    mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);
    mediaPlayer.prepare();
    }
       catch
        (IOException e) {
    mediaPlayer
       =
       null
       ;
    }
    }
    }


       private
       static
       final
       long
        VIBRATE_DURATION
       =
       200L
       ;


       private
       void
        playBeepSoundAndVibrate() {

       if
        (playBeep
       &&
        mediaPlayer
       !=
       null
       ) {
    mediaPlayer.start();
    }

       if
        (vibrate) {
    Vibrator vibrator
       =
        (Vibrator) getSystemService(VIBRATOR_SERVICE);
    vibrator.vibrate(VIBRATE_DURATION);
    }
    }


       /**
       
    * When the beep has finished playing, rewind to queue up another one.

       */
       

       private
       final
        OnCompletionListener beepListener
       =
       new
        OnCompletionListener() {

       public
       void
        onCompletion(MediaPlayer mediaPlayer) {
    mediaPlayer.seekTo(
       0
       );
    }
    };
       
      

    简化过的包结构图:
     
     简化后的ZXing 更加方便我们了解ZXing项目 是如何解码的。只要仔细查看源码,进行单点跟踪调试,相信大家很容易能理解。
    顾客是上帝
       很多人留言要源码, 其实我这不是什么源码,我只是把ZXing的东西简化了一下而已。事实上我也不喜欢直接放源码项目,这样大家就不想读ZXing的源码了。
    下面是我简化的版本:Zxing简化
    很多人需要Core 核心包(其实ZXing的源码里面就有),这里提供下我写文章时的版本 1.6的:
    Zxing
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-16 14:37 , Processed in 0.391985 second(s), 46 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表