您的位置:首页 > 移动开发 > 微信开发

(4.2.29) Android开发学习之基于ZBar实现微信扫一扫

2016-06-06 19:53 531 查看
蛰伏半月有余,一直在准备期末考试,期间抽空研究了一些Android的源代码,现在我就把在这其中的一些收获分享给大家。

今天想分享给大家的是二维码扫描。说起二维码,大家一定不会陌生,尤其是微信火了以后,在我们的生活中几乎随处都可以看到二维码的影

子。相关科技媒体甚至把二维码当成是未来移动互联网的入口,因此研究二维码的相关技术就显得意义非凡。目前在移动开发领域,使用最为广泛的二

维码库有两个,分别是ZXing和ZBar,其中ZXing在Android开发中较为常见,而ZBar则在iOS开发中较为常见,更重要的一点是,这两个库都是开源

的,因此我们可以从源代码中获得很多有用的东西。关于ZXing,网上有很多相关的博文,我今天不想多说,我今天想说的是ZBar,你可能会说,ZBar

不是用在IOS中,怎么今天要说ZBar呢?其实我是从这两个库使用的难易程度来选择的,ZXing功能强大,但是使用起来比较繁琐,网上有很多简化的

教程,大家可以自行前去研究。相比较而言,ZBar则比较简单,使用起来容易上手,因此我们今天选择了ZBar作为我们的库来使用。

一、准备工作

下载ZBar的SDK:由于ZBar的项目托管在sourceforge,所以在这里给出下载地址:http://download.csdn.net/detail/qinyuanpei/6794713

二、导入项目

下载完成后,我们直接解压,可以看到下面的目录结构



打开android文件夹,我们可以找到一个Example的文件夹,这是官方给出的示例代码,我们下面的所有工作都是基于这个示例程序而来。我们

自行创建一个Android项目,并将这两个文件拷贝到我们的项目中,同时引入ZBar相关的库文件。

三、建立布局

首先建立主界面,即扫描二维码的界面,界面布局代码如下:

[java] view
plain copy







<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

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

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent">

<RelativeLayout

android:layout_width="match_parent"

android:layout_height="40dp"

android:background="@drawable/title_bg" >

<TextView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:layout_centerVertical="true"

android:text="@string/Scan"

android:textColor="#ffffff"

android:textSize="18sp" />

<Button

android:id="@+id/BtnAbout"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentRight="true"

android:layout_alignParentTop="true"

android:text="@string/BtnAbout" />

</RelativeLayout>

<FrameLayout

android:id="@+id/cameraPreview"

android:layout_width="match_parent"

android:layout_height="0dip"

android:layout_weight="1"/>

</LinearLayout>

实现的布局效果如下图所示:



接下里,我们在来设计一个用于显示结果的界面,界面布局代码如下:

[html] view
plain copy







<?xml version="1.0" encoding="utf-8"?>

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

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="#ffffff"

android:orientation="vertical" >

<RelativeLayout

android:layout_width="match_parent"

android:layout_height="40dp"

android:background="@drawable/title_bg" >

<TextView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:layout_centerVertical="true"

android:textSize="18sp"

android:textColor="#ffffff"

android:text="@string/Result" />

<Button

android:id="@+id/BtnBack"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentLeft="true"

android:layout_alignParentTop="true"

android:text="@string/BtnBack" />

</RelativeLayout>

<TextView

android:id="@+id/TextResult"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:textColor="#000000"

android:layout_margin="8dp"

android:textIsSelectable="true"/>

</LinearLayout>

实现的界面效果如图所示:



四、编写代码

首先我们来写一个用于扫描的相机预览视图CameraPreview,此文件由ZBar的SDK提供,这里我做了下简单的修改

[java] view
plain copy







package com.Android.ZBar4Android;

import java.io.IOException;

import android.util.Log;

import android.view.SurfaceView;

import android.view.SurfaceHolder;

import android.annotation.SuppressLint;

import android.content.Context;

import android.graphics.Canvas;

import android.hardware.Camera;

import android.hardware.Camera.Parameters;

import android.hardware.Camera.PreviewCallback;

import android.hardware.Camera.AutoFocusCallback;

//此类由ZBar项目的SDK提供,我做了下修改

@SuppressLint("ViewConstructor")

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback

{

private SurfaceHolder mHolder;

private Camera mCamera;

private PreviewCallback mPreviewCallBack;

private AutoFocusCallback mAutoFocusCallBack;

public CameraPreview(Context context, Camera camera,

PreviewCallback previewCb,

AutoFocusCallback autoFocusCb) {

super(context);

mCamera = camera;

mPreviewCallBack = previewCb;

mAutoFocusCallBack = autoFocusCb;

/*

* 自动聚焦

* 要求API版本>9

*/

Camera.Parameters parameters = camera.getParameters();

for (String f : parameters.getSupportedFocusModes()) {

if (f == Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) {

parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);

mAutoFocusCallBack = null;

break;

}

}

// Install a SurfaceHolder.Callback so we get notified when the

// underlying surface is created and destroyed.

mHolder = getHolder();

mHolder.addCallback(this);

// deprecated setting, but required on Android versions prior to 3.0

mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

}

public void surfaceCreated(SurfaceHolder holder) {

// The Surface has been created, now tell the camera where to draw the preview.

try {

mCamera.setPreviewDisplay(holder);

} catch (IOException e) {

Log.d("DBG", "Error setting camera preview: " + e.getMessage());

}

}

public void surfaceDestroyed(SurfaceHolder holder) {

// Camera preview released in activity

}

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

/*

* If your preview can change or rotate, take care of those events here.

* Make sure to stop the preview before resizing or reformatting it.

*/

if (mHolder.getSurface() == null){

// preview surface does not exist

return;

}

// stop preview before making changes

try {

mCamera.stopPreview();

} catch (Exception e){

// ignore: tried to stop a non-existent preview

}

try {

// Hard code camera surface rotation 90 degs to match Activity view in portrait

mCamera.setDisplayOrientation(90);

mCamera.setPreviewDisplay(mHolder);

mCamera.setPreviewCallback(mPreviewCallBack);

mCamera.startPreview();

mCamera.autoFocus(mAutoFocusCallBack);

} catch (Exception e){

Log.d("DBG", "Error starting camera preview: " + e.getMessage());

}

}

/*

* 绘制校准框

* 修改:秦元培

* 时间:2013年11月22日

*

*/

@Override

protected void onDraw(Canvas mCanvas)

{

//这里不会写了?

}

}

接下来,我们来编写主界面的逻辑代码,在这里我们需要搞清楚的几个问题有:

1、相机的获取及相机的交互处理

2、二维码图片的获取

3、二维码图片的解析

对于第一个问题,需要我们深入地了解相机的工作原理,即我们需要了解Camera类。

获取相机的代码如下:

[java] view
plain copy







//获取照相机的方法

public static Camera getCameraInstance()

{

Camera mCamera = null;

try

{

mCamera = Camera.open();

//没有后置摄像头,尝试打开前置摄像头*******************

if (mCamera == null)

{

Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();

int cameraCount = Camera.getNumberOfCameras();

for (int camIdx = 0; camIdx < cameraCount; camIdx++)

{

Camera.getCameraInfo(camIdx, mCameraInfo);

if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)

{

mCamera = Camera.open(camIdx);

}

}

}

}

catch (Exception e)

{

e.printStackTrace();

}

return mCamera;

}

释放照相机的方法

[java] view
plain copy







//释放照相机

private void releaseCamera()

{

if (mCamera != null)

{

IsPreview = false;

mCamera.setPreviewCallback(null);

mCamera.release();

mCamera = null;

}

}

相机反馈的方法,即扫描到二维码后的处理

[java] view
plain copy







PreviewCallback previewCb = new PreviewCallback()

{

public void onPreviewFrame(byte[] data, Camera camera)

{

Camera.Parameters parameters = camera.getParameters();

//获取扫描图片的大小

Size mSize = parameters.getPreviewSize();

//构造存储图片的Image

Image mResult = new Image(mSize.width, mSize.height, "Y800");//第三个参数不知道是干嘛的

//设置Image的数据资源

mResult.setData(data);

//获取扫描结果的代码

int mResultCode = mScanner.scanImage(mResult);

//如果代码不为0,表示扫描成功

if (mResultCode != 0)

{

//停止扫描

IsPreview = false;

mCamera.setPreviewCallback(null);

mCamera.stopPreview();

//开始解析扫描图片

SymbolSet Syms = mScanner.getResults();

for (Symbol mSym : Syms)

{

//mSym.getType()方法可以获取扫描的类型,ZBar支持多种扫描类型,这里实现了条形码、二维码、ISBN码的识别

if (mSym.getType() == Symbol.CODE128 || mSym.getType() == Symbol.QRCODE ||

mSym.getType() == Symbol.CODABAR || mSym.getType() == Symbol.ISBN10 ||

mSym.getType() == Symbol.ISBN13|| mSym.getType()==Symbol.DATABAR ||

mSym.getType()==Symbol.DATABAR_EXP || mSym.getType()==Symbol.I25)

{

//添加震动效果,提示用户扫描完成

Vibrator mVibrator=(Vibrator)getSystemService(VIBRATOR_SERVICE);

mVibrator.vibrate(400);

Intent intent=new Intent(MainActivity.this,ResultActivity.class);

intent.putExtra("ScanResult", "扫描类型:"+GetResultByCode(mSym.getType())+"\n"+ mSym.getData());

//这里需要注意的是,getData方法才是最终返回识别结果的方法

//但是这个方法是返回一个标识型的字符串,换言之,返回的值中包含每个字符串的含义

//例如N代表姓名,URL代表一个Web地址等等,其它的暂时不清楚,如果可以对这个进行一个较好的分割

//效果会更好,如果需要返回扫描的图片,可以对Image做一个合适的处理

startActivity(intent);

IsScanned = true;

}

else

{

//否则继续扫描

IsScanned = false;

mCamera.setPreviewCallback(previewCb);

mCamera.startPreview();

IsPreview = true;

mCamera.autoFocus(autoFocusCB);

}

}

}

}

};

对于第二个问题,从上面的代码中我们可以看出,Image类用于获取二维码图片,ImageScanner类用于对图片的初步解析,而图片的最终解析是在SymbolSet类和

Symbol中去实现的,由此,第三个问题得以解答。下面给出完整代码:

[java] view
plain copy







/*

* ZBar4Android

* 作者:秦元培

* 时间:2013年12月21日

* 需要解决的问题有:

* 1、返回内容的正则解析

* 2、如果锁屏后打开程序会报错

* 3、没有校正框,画不出来啊,郁闷

* 4、可能会与其它相机应用冲突,如微信

* 5、条形码还是读不出来

*/

package com.Android.ZBar4Android;

import com.Android.ZBar4Android.CameraPreview;

import com.Android.ZBar4Android.R;

import android.app.Activity;

import android.app.AlertDialog;

import android.content.DialogInterface;

import android.content.Intent;

import android.content.pm.ActivityInfo;

import android.os.Bundle;

import android.os.Handler;

import android.os.Vibrator;

import android.view.LayoutInflater;

import android.view.View;

import android.view.ViewGroup.LayoutParams;

import android.view.Window;

import android.view.View.OnClickListener;

import android.widget.FrameLayout;

import android.widget.Button;

import android.widget.PopupWindow;

import android.hardware.Camera;

import android.hardware.Camera.PreviewCallback;

import android.hardware.Camera.AutoFocusCallback;

import android.hardware.Camera.Size;

import net.sourceforge.zbar.ImageScanner;

import net.sourceforge.zbar.Image;

import net.sourceforge.zbar.Symbol;

import net.sourceforge.zbar.SymbolSet;

import net.sourceforge.zbar.Config;

public class MainActivity extends Activity

{

//关于按钮

private Button BtnAbout;

//相机

private Camera mCamera;

//预览视图

private CameraPreview mPreview;

//自动聚焦

private Handler mAutoFocusHandler;

//图片扫描器

private ImageScanner mScanner;

//弹出窗口

private PopupWindow mPopupWindow;

//是否扫描完毕

private boolean IsScanned = false;

//是否处于预览状态

private boolean IsPreview = true;

//是否显示弹出层

private boolean IsShowPopup=false;

//加载iconvlib

static

{

System.loadLibrary("iconv");

}

public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

//去除标题栏

requestWindowFeature(Window.FEATURE_NO_TITLE);

setContentView(R.layout.layout_main);

//设置屏幕方向为竖屏

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

//自动聚焦线程

mAutoFocusHandler = new Handler();

//获取相机实例

mCamera = getCameraInstance();

if(mCamera == null)

{

//在这里写下获取相机失败的代码

AlertDialog.Builder mBuilder=new AlertDialog.Builder(this);

mBuilder.setTitle("ZBar4Android");

mBuilder.setMessage("ZBar4Android获取相机失败,请重试!");

mBuilder.setPositiveButton("确定", new DialogInterface.OnClickListener()

{

@Override

public void onClick(DialogInterface mDialogInterface, int mIndex)

{

MainActivity.this.finish();

}

});

AlertDialog mDialog=mBuilder.create();

mDialog.show();

}

//实例化Scanner

mScanner = new ImageScanner();

mScanner.setConfig(0, Config.X_DENSITY, 3);

mScanner.setConfig(0, Config.Y_DENSITY, 3);

//设置相机预览视图

mPreview = new CameraPreview(this, mCamera, previewCb, autoFocusCB);

FrameLayout preview = (FrameLayout)findViewById(R.id.cameraPreview);

preview.addView(mPreview);

if (IsScanned)

{

IsScanned = false;

mCamera.setPreviewCallback(previewCb);

mCamera.startPreview();

IsPreview = true;

mCamera.autoFocus(autoFocusCB);

}

//获取BtnAbout,显示程序信息

BtnAbout=(Button)findViewById(R.id.BtnAbout);

BtnAbout.setOnClickListener(new OnClickListener()

{

@Override

public void onClick(View v)

{

//如果弹出层已打开,销毁弹出层

if(IsShowPopup)

{

mPopupWindow.dismiss();

IsShowPopup=false;

}

else

{

//否则显示弹出层

mPopupWindow=new PopupWindow();

LayoutInflater mInflater=LayoutInflater.from(getApplicationContext());

View view=mInflater.inflate(R.layout.layout_about, null);

mPopupWindow.setContentView(view);

mPopupWindow.setWidth(LayoutParams.WRAP_CONTENT);

mPopupWindow.setHeight(LayoutParams.WRAP_CONTENT);

mPopupWindow.showAtLocation(mPreview, 0, 100, 100);

IsShowPopup=true;

}

}

});

}

//实现Pause方法

public void onPause()

{

super.onPause();

releaseCamera();

}

//获取照相机的方法

public static Camera getCameraInstance()

{

Camera mCamera = null;

try

{

mCamera = Camera.open();

//没有后置摄像头,尝试打开前置摄像头*******************

if (mCamera == null)

{

Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();

int cameraCount = Camera.getNumberOfCameras();

for (int camIdx = 0; camIdx < cameraCount; camIdx++)

{

Camera.getCameraInfo(camIdx, mCameraInfo);

if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)

{

mCamera = Camera.open(camIdx);

}

}

}

}

catch (Exception e)

{

e.printStackTrace();

}

return mCamera;

}

//释放照相机

private void releaseCamera()

{

if (mCamera != null)

{

IsPreview = false;

mCamera.setPreviewCallback(null);

mCamera.release();

mCamera = null;

}

}

private Runnable doAutoFocus = new Runnable()

{

public void run()

{

if (IsPreview)

mCamera.autoFocus(autoFocusCB);

}

};

PreviewCallback previewCb = new PreviewCallback()

{

public void onPreviewFrame(byte[] data, Camera camera)

{

Camera.Parameters parameters = camera.getParameters();

//获取扫描图片的大小

Size mSize = parameters.getPreviewSize();

//构造存储图片的Image

Image mResult = new Image(mSize.width, mSize.height, "Y800");//第三个参数不知道是干嘛的

//设置Image的数据资源

mResult.setData(data);

//获取扫描结果的代码

int mResultCode = mScanner.scanImage(mResult);

//如果代码不为0,表示扫描成功

if (mResultCode != 0)

{

//停止扫描

IsPreview = false;

mCamera.setPreviewCallback(null);

mCamera.stopPreview();

//开始解析扫描图片

SymbolSet Syms = mScanner.getResults();

for (Symbol mSym : Syms)

{

//mSym.getType()方法可以获取扫描的类型,ZBar支持多种扫描类型,这里实现了条形码、二维码、ISBN码的识别

if (mSym.getType() == Symbol.CODE128 || mSym.getType() == Symbol.QRCODE ||

mSym.getType() == Symbol.CODABAR || mSym.getType() == Symbol.ISBN10 ||

mSym.getType() == Symbol.ISBN13|| mSym.getType()==Symbol.DATABAR ||

mSym.getType()==Symbol.DATABAR_EXP || mSym.getType()==Symbol.I25)

{

//添加震动效果,提示用户扫描完成

Vibrator mVibrator=(Vibrator)getSystemService(VIBRATOR_SERVICE);

mVibrator.vibrate(400);

Intent intent=new Intent(MainActivity.this,ResultActivity.class);

intent.putExtra("ScanResult", "扫描类型:"+GetResultByCode(mSym.getType())+"\n"+ mSym.getData());

//这里需要注意的是,getData方法才是最终返回识别结果的方法

//但是这个方法是返回一个标识型的字符串,换言之,返回的值中包含每个字符串的含义

//例如N代表姓名,URL代表一个Web地址等等,其它的暂时不清楚,如果可以对这个进行一个较好的分割

//效果会更好,如果需要返回扫描的图片,可以对Image做一个合适的处理

startActivity(intent);

IsScanned = true;

}

else

{

//否则继续扫描

IsScanned = false;

mCamera.setPreviewCallback(previewCb);

mCamera.startPreview();

IsPreview = true;

mCamera.autoFocus(autoFocusCB);

}

}

}

}

};

//用于刷新自动聚焦的方法

AutoFocusCallback autoFocusCB = new AutoFocusCallback()

{

public void onAutoFocus(boolean success, Camera camera)

{

mAutoFocusHandler.postDelayed(doAutoFocus, 1000);

}

};

//根据返回的代码值来返回相应的格式化数据

public String GetResultByCode(int CodeType)

{

String mResult="";

switch(CodeType)

{

//条形码

case Symbol.CODABAR:

mResult="条形码";

break;

//128编码格式二维码)

case Symbol.CODE128:

mResult="二维码";

break;

//QR码二维码

case Symbol.QRCODE:

mResult="二维码";

break;

//ISBN10图书查询

case Symbol.ISBN10:

mResult="图书ISBN号";

break;

//ISBN13图书查询

case Symbol.ISBN13:

mResult="图书ISBN号";

break;

}

return mResult;

}

}

对于显示扫描结果的界面,代码比较简单

[java] view
plain copy







/*

* 返回扫描结果

* 作者:秦元培

* 时间:2013年12月21日

* 总结:这里有一个问题,就是在这个界面上按下返回键的时候程序会立即报错,试着重写过相关的方法都解决不了问题

* 谁要是知道这个问题怎么解决,记得给我说一声啊

*/

package com.Android.ZBar4Android;

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.view.Window;

import android.widget.Button;

import android.widget.TextView;

public class ResultActivity extends Activity

{

private TextView tv;

private Button BtnBack;

@Override

protected void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

requestWindowFeature(Window.FEATURE_NO_TITLE);

setContentView(R.layout.layout_result);

//获取扫描结果

Intent intent=getIntent();

Bundle mData=intent.getExtras();

CharSequence mResult=mData.getCharSequence("ScanResult");

StringHelper mHelper=new StringHelper(mResult.toString());

mResult=mHelper.SplitFormDict();

tv=(TextView)findViewById(R.id.TextResult);

tv.setText(mResult);

//返回扫描界面

BtnBack=(Button)findViewById(R.id.BtnBack);

BtnBack.setOnClickListener(new OnClickListener()

{

@Override

public void onClick(View arg0)

{

Intent intent=new Intent(ResultActivity.this,MainActivity.class);

startActivity(intent);

}

});

}

@Override

protected void onPause()

{

super.onPause();

}

}

五、总结

经过测试,可以快速地对二维码进行识别,并显示扫描结果。目前尚存在的问题有:

1、官方的文档说它是支持条形码、ISBN、二维码等多种形式的编码的,并且在程序代码中亦有所体现,但是实际测试中,发现二维码可以扫,其余的无法扫描

2、锁屏后再次打开程序会报错

3、与微信等类似的需要相机功能的软件冲突

4、校准框死活画不出来

5、在扫描结果界面下按下返回键,程序报错,无法拦截

欢迎大家积极寻找解决问题的方法,再次谢谢大家!

源代码下载



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: