文档章节

利用ZXING扫描二维码的分析

lifer
 lifer
发布于 2014/05/08 23:35
字数 2051
阅读 431
收藏 3

之前给公司做了一个摄影相关的应用,现在要添加二维码扫描的功能,网上找资料后,虽然已经成功集成到app里面,但是总感觉心里没底儿。所以趁这段时间不是很忙,总结一下。

首先是启动扫描的UI类:

1,Activity启动,当然是onCreate方法

private CaptureActivityHandler handler;
private ViewfinderView viewfinderView;
private boolean hasSurface;
private Vector<BarcodeFormat> decodeFormats;
private String characterSet;
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.activity_capture);
    // ViewUtil.addTopView(getApplicationContext(), this,
    // R.string.scan_card);
    CameraManager.init(getApplication());
    viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);

    Button mButtonBack = (Button) findViewById(R.id.button_back);
    mButtonBack.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            Scaner.this.finish();
        }
    });
    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();
}

/**
 * 
* 
* 
* @param @param result
* @param @param barcode

* @author  Administrator
* @return void
 */
public void handleDecode(Result result, Bitmap barcode) {
    inactivityTimer.onActivity();
    playBeepSoundAndVibrate();
    String resultString = result.getText();
    if (resultString.equals("")) {
        Toast.makeText(Scaner.this, "Scan failed!",3000).show();
    } else {
        //查询keycode 本地数据库 1,优先查询本地库,2,没有本地库,直接跳到知道链接
        //分析出keyCode
        Log.i("testMain","scan_result=====>"+resultString);

        String keyCode="";
        String[] split1;
        if(resultString.lastIndexOf("?")<0){
            Intent intent = new Intent(this, InnerBrowser.class);
            Bundle bundle = new Bundle();
            bundle.putString("result", resultString);
            //bundle.putParcelable("bitmap", barcode);
            intent.putExtras(bundle);
            startActivity(intent);Scaner.this.finish();return;
        }
        String[] attr = resultString.substring(resultString.lastIndexOf("?")-1, resultString.length()).split("&");
        for (String string : attr) {
             split1 = string.split("=");
            if(split1[0].equalsIgnoreCase("keycode")){
                //找到
                if(split1.length==2){
                    keyCode=split1[1];
                }
            }
        }
        Log.i("testMain","keyCode=====>"+keyCode);
        if(!StringUtils.isBlank(keyCode)){
            AttractionDAO dao=new AttractionDAO(Scaner.this);
            Attraction a=dao.findAttrByKeyCode(keyCode);
            Log.i("testMain","a=====>"+a);
            if(a!=null){
                Intent it=new Intent();
                it.setClass(Scaner.this, UIAttractionDetail.class);
                it.putExtra("a", a);
                startActivity(it);
            }else{
                Intent intent = new Intent(this, InnerBrowser.class);
                Bundle bundle = new Bundle();
                bundle.putString("result", resultString);
                //bundle.putParcelable("bitmap", barcode);
                intent.putExtras(bundle);
                startActivity(intent);
                //this.setResult(RESULT_OK, resultIntent);
                //使用内置浏览器打开网站内容
            }
        }else{
            Intent intent = new Intent(this, InnerBrowser.class);
            Bundle bundle = new Bundle();
            bundle.putString("result", resultString);
            //bundle.putParcelable("bitmap", barcode);
            intent.putExtras(bundle);
            startActivity(intent);
            //this.setResult(RESULT_OK, resultIntent);
            //使用内置浏览器打开网站内容
        }
    }
    Scaner.this.finish();
}

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();

}

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);
    }
};

从上面代码可以看出,做了三件事儿:加载布局文件;初始化了一个相机管理器;设置按钮监听,初始化了一个InactivityTimer实例;

然后,最重要的是他实现了一个CallBack函数:具体参见: 

SurfaceHolder.Callback 译文


此时,

surfaceCreated

   

这个方法会调用然后就初始化相机的一些参数:


   

前两个我们好理解,第三个是干嘛的?

我们先看布局文件:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

        <SurfaceView
            android:id="@+id/preview_view"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_gravity="center" />

        <com.euc.app.scan.view.ViewfinderView
            android:id="@+id/viewfinder_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <include
            android:id="@+id/include1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            layout="@layout/activity_title" />
    </RelativeLayout>

</FrameLayout>

   

可以看到里面有一个自定义的View及surfaceView,

对于我这样的初学者来说,surfaceView 是什么东西?

csdn上看到这个文章

Android中SurfaceView的使用详解    

虽然不是很明白,但是大致明白这是个什么东西了。

了解了生命周期之后,我们来看他执行的方法:

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;

    }

   

这个surfaceView 创建出来之后,其实也把摄像头的配置信息以及硬件信息初始化好了。

OK,经过上面一个oncreate以及布局文件的加载,我们已经知道,摄像头预览成功,

这个自定义的View又是干嘛的?我们继续看源码:
   

private final int maskColor;
private final int resultColor;

private final int resultPointColor;
private Collection<ResultPoint> possibleResultPoints;
private Collection<ResultPoint> lastPossibleResultPoints;

boolean isFirst;

public ViewfinderView(Context context, AttributeSet attrs) {
    super(context, attrs);

    density = context.getResources().getDisplayMetrics().density;
    //将像素转换成dp
    ScreenRate = (int)(20 * density);

    paint = new Paint();
    Resources resources = getResources();
    maskColor = resources.getColor(R.color.viewfinder_mask);
    resultColor = resources.getColor(R.color.result_view);

    resultPointColor = resources.getColor(R.color.possible_result_points);
    possibleResultPoints = new HashSet<ResultPoint>(5);
}

@Override
public void onDraw(Canvas canvas) {
    //中间的扫描框,你要修改扫描框的大小,去CameraManager里面修改
    Rect frame = CameraManager.get().getFramingRect();
    if (frame == null) {
        return;
    }

    //初始化中间线滑动的最上边和最下边
    if(!isFirst){
        isFirst = true;
        slideTop = frame.top;
        slideBottom = frame.bottom;
    }

    //获取屏幕的宽和高
    int width = canvas.getWidth();
    int height = canvas.getHeight();

    paint.setColor(resultBitmap != null ? resultColor : maskColor);

    //画出扫描框外面的阴影部分,共四个部分,扫描框的上面到屏幕上面,扫描框的下面到屏幕下面
    //扫描框的左边面到屏幕左边,扫描框的右边到屏幕右边
    canvas.drawRect(0, 0, width, frame.top, paint);
    canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
    canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1,
            paint);
    canvas.drawRect(0, frame.bottom + 1, width, height, paint);



    if (resultBitmap != null) {
        // Draw the opaque result bitmap over the scanning rectangle
        paint.setAlpha(OPAQUE);
        canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint);
    } else {

        //画扫描框边上的角,总共8个部分
        paint.setColor(Color.GREEN);
        canvas.drawRect(frame.left, frame.top, frame.left + ScreenRate,
                frame.top + CORNER_WIDTH, paint);
        canvas.drawRect(frame.left, frame.top, frame.left + CORNER_WIDTH, frame.top
                + ScreenRate, paint);
        canvas.drawRect(frame.right - ScreenRate, frame.top, frame.right,
                frame.top + CORNER_WIDTH, paint);
        canvas.drawRect(frame.right - CORNER_WIDTH, frame.top, frame.right, frame.top
                + ScreenRate, paint);
        canvas.drawRect(frame.left, frame.bottom - CORNER_WIDTH, frame.left
                + ScreenRate, frame.bottom, paint);
        canvas.drawRect(frame.left, frame.bottom - ScreenRate,
                frame.left + CORNER_WIDTH, frame.bottom, paint);
        canvas.drawRect(frame.right - ScreenRate, frame.bottom - CORNER_WIDTH,
                frame.right, frame.bottom, paint);
        canvas.drawRect(frame.right - CORNER_WIDTH, frame.bottom - ScreenRate,
                frame.right, frame.bottom, paint);


        //绘制中间的线,每次刷新界面,中间的线往下移动SPEEN_DISTANCE
        slideTop += SPEEN_DISTANCE;
        if(slideTop >= frame.bottom){
            slideTop = frame.top;
        }
        canvas.drawRect(frame.left + MIDDLE_LINE_PADDING, slideTop - MIDDLE_LINE_WIDTH/2, frame.right - MIDDLE_LINE_PADDING,slideTop + MIDDLE_LINE_WIDTH/2, paint);


        //画扫描框下面的字
        paint.setColor(Color.WHITE);
        paint.setTextSize(TEXT_SIZE * density);
        paint.setAlpha(0x40);
        paint.setTypeface(Typeface.create("System", Typeface.BOLD));
        canvas.drawText(getResources().getString(R.string.scan_text), frame.left, (float) (frame.bottom + (float)TEXT_PADDING_TOP *density), paint);



        Collection<ResultPoint> currentPossible = possibleResultPoints;
        Collection<ResultPoint> currentLast = lastPossibleResultPoints;
        if (currentPossible.isEmpty()) {
            lastPossibleResultPoints = null;
        } else {
            possibleResultPoints = new HashSet<ResultPoint>(5);
            lastPossibleResultPoints = currentPossible;
            paint.setAlpha(OPAQUE);
            paint.setColor(resultPointColor);
            for (ResultPoint point : currentPossible) {
                canvas.drawCircle(frame.left + point.getX(), frame.top
                        + point.getY(), 6.0f, paint);
            }
        }
        if (currentLast != null) {
            paint.setAlpha(OPAQUE / 2);
            paint.setColor(resultPointColor);
            for (ResultPoint point : currentLast) {
                canvas.drawCircle(frame.left + point.getX(), frame.top
                        + point.getY(), 3.0f, paint);
            }
        }


        //只刷新扫描框的内容,其他地方不刷新
        postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top,
                frame.right, frame.bottom);

    }
}

public void drawViewfinder() {
    resultBitmap = null;
    invalidate();
}

/**
 * Draw a bitmap with the result points highlighted instead of the live
 * scanning display.
 * 
 * @param barcode
 *            An image of the decoded barcode.
 */
public void drawResultBitmap(Bitmap barcode) {
    resultBitmap = barcode;
    invalidate();
}

public void addPossibleResultPoint(ResultPoint point) {
    possibleResultPoints.add(point);
}

   

哦,这个就是定义了一个有动态效果的扫描界面
   


上面的虽然代码不多,当时我们现在回忆一下步骤:

1,启动activity,加载布局文件,初始化surfaceView,初始化自定义的View(动态界面),

2,在初始化surfaceView的时候,同时初始化了摄像头的参数,初始化的handler处理器,启动了摄像头预览。


问题:那什么时候开始监听扫描二维码的呢?

初始化handler 的时候就开始监听了,看一下其构造函数:
   

public CaptureActivityHandler(Scaner activity, Vector<BarcodeFormat> decodeFormats,
      String characterSet) {
    this.activity = activity;
    decodeThread = new DecodeThread(activity, decodeFormats, characterSet,
        new ViewfinderResultPointCallback(activity.getViewfinderView()));
    decodeThread.start();
    state = State.SUCCESS;
    // Start ourselves capturing previews and decoding.
    CameraManager.get().startPreview();
    restartPreviewAndDecode();
  }

   

再来一个:上面构造函数new了一个对象,这个对象就是用来监听获取扫描的图像的。

直到获取了二维码图像,调用回调函数就结束。

final class DecodeThread extends Thread {

  public static final String BARCODE_BITMAP = "barcode_bitmap";
  private final Scaner activity;
  private final Hashtable<DecodeHintType, Object> hints;
  private Handler handler;
  private final CountDownLatch handlerInitLatch;

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

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

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

    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);
  }

  Handler getHandler() {
    try {
      handlerInitLatch.await();
    } catch (InterruptedException ie) {
      // continue?
    }
    return handler;
  }

  @Override
  public void run() {
    Looper.prepare();
    handler = new DecodeHandler(activity, hints);
    handlerInitLatch.countDown();
    Looper.loop();
  }

}

   

回调函数:
   

public void handleDecode(Result result, Bitmap barcode) {
    inactivityTimer.onActivity();
    playBeepSoundAndVibrate();
    String resultString = result.getText();
    if (resultString.equals("")) {
        Toast.makeText(Scaner.this, "Scan failed!",3000).show();
    } else {  
        //扫描结果的处理。
    }
    Scaner.this.finish();
}

   



     LIFE日志


© 著作权归作者所有

共有 人打赏支持
lifer
粉丝 7
博文 17
码字总数 15164
作品 0
朝阳
程序员
私信 提问
zxing 如何识别反转二维码

说起二维码扫描,估计很多人用的是 zxing 吧。 然而 zxing 虽然好用,但是却有一些坑。 这边分析一下自己实际项目遇到的一个坑。 什么坑呢? 下面举个栗子你就懂了。 这边生成二维码使用的是...

AndroidTraveler
07/23
0
0
Android微信扫描二维码登入实现 基于ZXing开源工程

项目需求,做一个类似微信扫描二维码,网页端登陆的功能实现。 利用开源项目ZXing来实现。ZXing是一个开源Java类库用于解析多种格式的1D/2D条形码。目标是能够对QR编码、Data Matrix、UPC的1...

jeffzhao
2013/02/02
0
7
利用iText和zxing生成和读pdf417二维码

前面的一些博文中已经提到了zxing这个开源工具生成和读取二维码图片,仅从学习的角度来看,可以告一个段落。在实际的生产环境中,应用zxing生成和读取二维码,却存在一些问题: 使用扫描枪读...

彭苏云
2014/03/06
0
0
android 二维码扫描算法优化问题

哪位大虾做过android二维码的优化问题么?最近在搞一个二维码扫描功能,zxing、zbar都试了下,感觉zbar更快吧,但是zbar是c写的,封装成so库看不到算法啊,找到文档都是ios的。。然后zxing反...

hzy00
2014/07/28
6.7K
1
基于zxing生成与解析二维码、条形码

基于zxing(https://github.com/zxing/zxing)与maven,针对二维码(包括带图片的二维码)、条形码进行了简单的封装,以便在项目中更好的利用。 ZXing是一个开源Java类库用于解析多种格式的1D/...

cloud-coder
2014/03/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

错误: 找不到或无法加载主类

在IDEA的使用过程中,经常断掉服务或者重启服务,最近断掉服务重启时突然遇到了一个启动报错: 错误:找不到或无法加载主类 猜测:1,未能成功编译; 尝试:菜单---》Build---》Rebuild Pro...

安小乐
12分钟前
1
0
vue路由传参,刷新页面,引发的bug

最近遇到一个bug 通过vue路由跳转到页面, 然后接参控制(v-if ),成功显示 而刷新页面,显示失败。 苦苦地找了半天原因,打印参数发现正确,再打印下类型......,路由跳过来会保持传参时的...

hanbb
13分钟前
0
0
【58沈剑 架构师之路】InnoDB,select为啥会阻塞insert?

MySQL的InnoDB的细粒度行锁,是它最吸引人的特性之一。 但是,如《InnoDB,5项最佳实践》所述,如果查询没有命中索引,也将退化为表锁。 InnoDB的细粒度锁,是实现在索引记录上的。 一,Inn...

张锦飞
16分钟前
0
0
冒泡,选择和插入排序比较

/** * 冒泡排序,两层嵌套循环,内层局部比较后,找出最大或者最小数据并交换数据,使其局部有序,外层用于比较剩余元素,相较于选择排序,选择排序相当于是冒泡的一个优化版本(减少了交换...

strict_nerd
17分钟前
0
0
html内联(行内)元素、块级(块状)元素和行内块元素分类

HTML可以将元素分类方式分为内联(行内)元素、块级(块状)元素和行内块元素三种。 注:HTML是标签语言,那么既然是标签,就可以自己定义一些自己元素(如<wode>自定义的元素</wode>等),自...

NB-One
22分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部