Android ZXing条码扫描简化

前言

  最近公司的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 publicvoid 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 protectedvoid 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 privatevoid 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 publicvoid surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }
    @Override
publicvoid surfaceCreated(SurfaceHolder holder) { if (!hasSurface) {             hasSurface =true;             initCamera(holder);         }
    }
    @Override
publicvoid 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 里实现它。

复制代码
publicvoid handleDecode(Result obj, Bitmap barcode) {         inactivityTimer.onActivity();         viewfinderView.drawResultBitmap(barcode);          playBeepSoundAndVibrate();         txtResult.setText(obj.getBarcodeFormat().toString() +":"+ obj.getText());     }
复制代码

最后

  ZXing的简化已基本完成,有几位是可以运行成功的?呵呵。

下面是CaptureActivity的源码:

CaputreActivity
publicclass CaptureActivity extends Activity implements Callback {
private CaptureActivityHandler handler; private ViewfinderView viewfinderView; privateboolean hasSurface; private Vector<BarcodeFormat> decodeFormats; private String characterSet; private TextView txtResult; private InactivityTimer inactivityTimer; private MediaPlayer mediaPlayer; privateboolean playBeep; privatestaticfinalfloat BEEP_VOLUME =0.10f; privateboolean vibrate;
/** Called when the activity is first created. */     @Override publicvoid 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
protectedvoid 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
protectedvoid onPause() { super.onPause(); if (handler !=null) {             handler.quitSynchronously();             handler =null;         }         CameraManager.get().closeDriver();     }
    @Override
protectedvoid onDestroy() {         inactivityTimer.shutdown(); super.onDestroy();     }
privatevoid 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
publicvoid surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }
    @Override
publicvoid surfaceCreated(SurfaceHolder holder) { if (!hasSurface) {             hasSurface =true;             initCamera(holder);         }
    }
    @Override
publicvoid surfaceDestroyed(SurfaceHolder holder) {         hasSurface =false;
    }
public ViewfinderView getViewfinderView() { return viewfinderView;     }
public Handler getHandler() { return handler;     }
publicvoid drawViewfinder() {         viewfinderView.drawViewfinder();
    }
publicvoid handleDecode(Result obj, Bitmap barcode) {         inactivityTimer.onActivity();         viewfinderView.drawResultBitmap(barcode);          playBeepSoundAndVibrate();         txtResult.setText(obj.getBarcodeFormat().toString() +":"+ obj.getText());     }
privatevoid 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;             }         }     }
privatestaticfinallong VIBRATE_DURATION =200L;
privatevoid 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. */privatefinal OnCompletionListener beepListener =new OnCompletionListener() { publicvoid onCompletion(MediaPlayer mediaPlayer) {             mediaPlayer.seekTo(0);         }     };

简化过的包结构图:

 

 简化后的ZXing 更加方便我们了解ZXing项目 是如何解码的。只要仔细查看源码,进行单点跟踪调试,相信大家很容易能理解。

posted @ 2014-02-21 09:33  萧萧  阅读(305)  评论(0)    收藏  举报