• 条码扫描二维码扫描——ZXing android 源码简化


    条码扫描二维码扫描——ZXing android 源码简化 - DotDot - 博客园

    条码扫描二维码扫描——ZXing android 源码简化

    前言

    最近公司的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项目 是如何解码的。只要仔细查看源码,进行单点跟踪调试,相信大家很容易能理解。

    顾客是上帝

       很多人留言要源码, 其实我这不是什么源码,我只是把ZXing的东西简化了一下而已。事实上我也不喜欢直接放源码项目,这样大家就不想读ZXing的源码了。

    下面是我简化的版本:Zxing简化

    很多人需要Core 核心包(其实ZXing的源码里面就有),这里提供下我写文章时的版本 1.6的:

    Zxing

  • 相关阅读:
    24. orcle创建备份表
    23. oralce11g导出dmp然后导入Oracle10g
    模板
    日记集
    Outlook 2013 电子邮件账户设置备份与恢复
    Firefox恢复书签
    Windows 7 Professional安装多语言包
    CentOS下成功修复了Windows的grub引导
    CentOS 7修复MBR和GRUB
    从移动硬盘开机,引导VHD(Win10)
  • 原文地址:https://www.cnblogs.com/lexus/p/2878397.html
Copyright © 2020-2023  润新知