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

蛰伏半月有余,一直在准备期末考试,期间抽空研究了一些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相关的库文件。


三、建立布局

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

<?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>

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


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

<?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提供,这里我做了下简单的修改

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类。

获取相机的代码如下:

//获取照相机的方法
    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;
        }
    }


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

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中去实现的,由此,第三个问题得以解答。下面给出完整代码:

/*
 * 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;
    }
 
    
}
对于显示扫描结果的界面,代码比较简单

/*
 * 返回扫描结果
 * 作者:秦元培
 * 时间: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、在扫描结果界面下按下返回键,程序报错,无法拦截

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

源代码下载


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值