对于刚开始学习android开发的童鞋们来说,若有一个简单而又全面的android工程能来剖析,那就是再好不过了,zxing就是不错得例子。
    zxing的源码可以到google code上下载,整个源码check out 下来,里面有各个平台的源码,ios的,android的。当然我们需要的就是android代码。
    将android的工程导入到eclipse中,导入完成后,eclipse会显示各种错误,这是缺少core文件夹里面的核心库文件所致,在project中创建文件夹core,再将zxing源码中得core文件夹下得代码导入进来,这样就可以了。
    如果遇到unable resolved target-X,则是你的avd版本问题,可以在project.propertities修改target值。clean下就ok。
    如上的都是zxing android代码分析的准备,下面的则是正式开始。

如上图:为整个android工程的代码,android入门就重这些代码着手。其中主要关注的是android,camera,encode,result文件夹。
   程序启动的流程:加载main activity,在此类中创建CaptureActivityHandler对象,该对象启动相机,实现自动聚焦,创建DecodeThread线程,DecodeThread创建Decodehandler,这个对象就获取从相机得到的原始byte数据,开始解码的第一步工作,从获取的byte中解析qr图来,并解析出qr图中的字符,将这块没有分析的字符抛送到CaptureActivityHandler中handle,该类调用main activity的decode函数完成对字符的分析,最后显示在界面上(刷新UI,最好在UI线程里完成)。这样一个解析qr图的过程并完成。
   下面具体分析整个过程。重点之处有main activity,camera.
   程序启动的第一个activity便是:CaptureActivity,有点类似于c中的main函数,在此是main activity。这个acitvity做的主要的事便是:加载扫描各种条形码,二维码的一个界面,启动一个处理获取一维码二维码信息的线程,完成对于获取的图像信息进行解码,最后再将解码的信息显示在界面上。

完成界面的加载主要在于onCreate,和onResume函数中,这涉及到了一个activity的生命周期,以后再具体分析。首先调用onCreate,再调用onResume,在onResume中会判断这个activity是由什么启动的,可能是其他的app触发了,也可能是用户直接启动的。这样就初始化了三个变量,一是source,便是启动activity的源,一是decodeFormats,指出解码的方式,是qr,还是其他的等等,最后一个是:charactreset,即是对于这些生成qr图的字符的编码方式。若没有对core中得代码修改,用该程序解析GB2312编码的字符则会乱码。乱码的解决后面将提到。
   界面的加载中有两个很关键的类。surfaceview 和 ViewFinderView,前面的是用来加载从底层硬件获取的相机取景的图像,后面的是自定义的view,实现了扫描时的界面,不停的刷新,并将识别的一些数据,如定位的点回调显示在界面上。

上面介绍了zxing扫描二维码的过程,刚开始看这份代码时,不怎么明白,很多细节都不清楚,到后来又了更深的理解后,发现这代码设计的就是好,质量高。整个扫描二维码和一维码的过程是非常迅速的,效率很高。最近发现微博上有个二维坊的ID,发得qr码图形都非常的Q,不知道怎么弄出来的,程序员可以借这个可爱的qr码浪漫下。
   在整个zxing的android代码部分,很重要的两点是main activity 和 camera。在这一篇,就主要介绍下android camera的使用。打开zxing下的Barcode scanner,并会有如下的界面。为了更好的理解camera,先介绍这个界面。

刚开始接触到android时,对此界面一点不熟悉。后面认真看了其中的代码,明白了一点点。 这个界面的定义主要在ViewfinderView.java这个类中,这个类继承了View类,实现了自定义的View。View就是对应于屏幕的一个画布,可以在这个屏幕上任意绘制你想要的设计。最重要的重载onDraw函数,在其中实现绘制。就来看下ViewfinderView是如何实现界面上的感觉的。
    画面中一共分为两块:外边半透明的一片,中间全透明的一片。外面半透明的画面是由四个矩形组成。

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

drawRect函数有五个参数,前四个参数构成两个坐标,组成一个矩形,后面一个画笔相关的。
   中间的全透明一块,也是由四个矩形组成,只是每个矩形很窄,才一两个像素,成了一条直线。

paint.setColor(frameColor);
canvas.drawRect(frame.left, frame.top, frame.right + 1, frame.top + 2, paint);
canvas.drawRect(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1, paint);
canvas.drawRect(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1, paint);
canvas.drawRect(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1, paint);

最中间的一条红色扫描线亦如此。
   onDraw()函数的最后一句是:

postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom);

这一句很关键,postInvalidateDelayed函数主要用来在非UI线程中刷新UI界面,每个ANIMATION_DELAY时间,刷新指定的范围。所以会不停得调用onDraw函数,并在界面上添加绿色的特征点。在刚开始看这份代码时,没明白是如何添加绿色的标记点的。现在再看了一遍,大致明白了。在camera聚焦获取图片后,再使用core中的库进行解析,会得出特征点的坐标,最后通过ViewfinderResultPointCallback类回调,将特征点添加到ViewfinderView中的ArrayList容器中。

public void foundPossibleResultPoint(ResultPoint point) {
viewfinderView.addPossibleResultPoint(point);
}

这个函数特征点加入到possibleResultPoints中,由于对java不熟悉,不知道 “=” 的赋值对于List来说是浅拷贝,总在想possibleResultPoints对象没有被赋值,如何获取这些特征点了。后面才知道,这个“=”赋值,只是个浅拷贝。若要对这种预定义的集合实现深拷贝,可以使用构造函数,
如:List<ResultPoint> points = new List<ResultPoint>(possibleResultPoints);

public void addPossibleResultPoint(ResultPoint point) {
   List<ResultPoint> points = possibleResultPoints;
   synchronized (point) {
       points.add(point);
       int size = points.size();
       if (size > MAX_RESULT_POINTS) {
         // trim it
         points.subList(0, size - MAX_RESULT_POINTS / 2).clear();
       }
   }
   }

ViewfinderView自定义了view,实现了一个简洁的扫描界面。这一篇记录我再看代码过程中对于Android Camera 的理解。由于才开始写技术类博客,前两篇有很多不足
之处,都是自己随性而写,估计大家很难对我写的有一个清晰的了解。这篇尝试改变下风格,争取好好的表达我的浅薄理解,也让大家能够看懂。
      在看Barcode Scanner中关于camera代码前,先对android camera开发做个简单的介绍,算是入门。
      首先是使用camera需要用到的权限。

<uses-permission android:name="android.permission.CAMERA"/>
< uses-feature android:name="android.hardware.camera"/>

如下是一个很简单的camera示例,简单到只能取景,即打开相机,将景象显示在屏幕上,仅此而已。

import java.io.IOException;
import android.app.Activity;
import android.hardware.Camera;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class CameraTestActivity extends Activity implements SurfaceHolder.Callback {
   private SurfaceHolder surfaceHolder;
   private Camera camera;
   /** Called when the activity is first created. */
   @Override
   public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
         SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
         surfaceHolder = surfaceView.getHolder();
         surfaceHolder.addCallback(this);
         surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
   }
   @Override
   public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
         // TODO Auto-generated method stub
   }
   @Override
   public void surfaceCreated(SurfaceHolder arg0) {
         // TODO Auto-generated method stub
         camera = Camera.open();

Camera.Parameters parameters = camera.getParameters();
         parameters.setPreviewSize(480, 320); // 设置
         camera.setParameters(parameters);
         try {
             camera.setPreviewDisplay(surfaceHolder);
         } catch (IOException e) {
             System.out.println(e.getMessage());
         }
         camera.startPreview();
   }
   @Override
   public void surfaceDestroyed(SurfaceHolder arg0) {
         // TODO Auto-generated method stub
         if (camera != null) {
             camera.stopPreview();
         }
         camera.release();
         camera = null;
   }
}

其中的R.id.preview_view如下:

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

首先这个activity实现了SurfaceHolder.Callback接口,并重写了这个接口的三个方法。

surfaceview总之能够获相机硬件捕捉到的数据并显示出来,在上面的代码中,先初始化了surfaceholder对象。并重写了surfaceCreated函数,在这个函数中,完成了对相机打开取景的基本操作。首先是Camera.open()获取一个Camera对象,在初始化一些camera参数,如图像格式,图像预览大小,刷新率等等。在设置预览显示,最后别忘了startPreview,则完成了取景。由于刚开始开发的工程需要将相机的取景设置为竖屏的,Barcode Scanner设置的是横屏的,开始再尝试调整图片显示方向时,我以为是再manifest中重新设置,

android:screenOrientation="landscape"

将landscape该为portrait,结果却很意外,屏幕是竖着显示了,但是取景后的内容与显示却是横竖相反的,手机竖着取景,显示的却是横着的。不可以简单的通过调整这个参数值来改变方向。后面调用下面这个函数,重新设置了预览照片的显示方向。

camera.setDisplayOrientation(90);

调整显示方向后,取景终于正常了。但是在后面预览拍照结果时,发现这都是假象,相机底层取景还是横屏的,只是在预览时进行了方向调整,这样还存在一个显示照片拉伸的问题。这个没有深入查看了。

Camera取景后显示于屏幕上,是个挺简单的过程,但这会出现各种意料不到的问题,例如之前说的屏幕横竖屏与预览图片之间的方向,图片拉伸,还有在Barcode Scanner中,简单的旋转了图片预览方向后,会出现特征点标记错位,等等。
   第三篇简单的完成了相机的取景,还没有将取景的图片拍照存储下来。若想实现拍照的效果,则需要实现回调函数:Camera.PreviewCallback接口。接上一篇的代码,在此实现拍照的功能,将图片显示出来。之前一直在看Barcode Scanner的源码,并只是在其代码上修剪。当昨天自己来实现Camera的自动聚焦时,并遇到比较纠结的问题。在不出意外的情况下,Camera的使用还是挺简单的。
   先在此贴出代码,最简单,代码经过了测试,正常运行,测试机是HTC MyTouch 3G slide。
   需要的权限:

<uses-permission android:name="android.permission.CAMERA" /><uses-feature android:name="android.hardware.camera" /><uses-feature android:name="android.hardware.camera.autofocus" />

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.widget.ImageView;

public class CameraTestActivity extends Activity implements SurfaceHolder.Callback {
   private static String TAG = CameraTestActivity.class.getSimpleName();
   private SurfaceHolder surfaceHolder;
   private Camera camera;
   private ImageView imageView;
   private Timer mTimer;
   private TimerTask mTimerTask;

private Camera.AutoFocusCallback mAutoFocusCallBack;
   private Camera.PreviewCallback previewCallback;

/** Called when the activity is first created. */
   @Override
   public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
         SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
         imageView = (ImageView) findViewById(R.id.image_view);
         surfaceHolder = surfaceView.getHolder();
         surfaceHolder.addCallback(this);
         surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
         mAutoFocusCallBack = new Camera.AutoFocusCallback() {
             @Override
             public void onAutoFocus(boolean success, Camera camera) {
               if (success) {
                     // isAutoFocus = true;
                     camera.setOneShotPreviewCallback(previewCallback);
                     Log.d(TAG, "onAutoFocus success");
               }
             }
         };

previewCallback = new Camera.PreviewCallback() {
             @Override
             public void onPreviewFrame(byte[] data, Camera arg1) {
               if (data != null)
               {
                     Camera.Parameters parameters = camera.getParameters();
                     int imageFormat = parameters.getPreviewFormat();
                     Log.i("map", "Image Format: " + imageFormat);

Log.i("CameraPreviewCallback", "data length:" + data.length);
                     if (imageFormat == ImageFormat.NV21)
                     {
                         // get full picture
                         Bitmap image = null;
                         int w = parameters.getPreviewSize().width;
                         int h = parameters.getPreviewSize().height;
                           
                         Rect rect = new Rect(0, 0, w, h);
                         YuvImage img = new YuvImage(data, ImageFormat.NV21, w, h, null);
                         ByteArrayOutputStream baos = new ByteArrayOutputStream();
                         if (img.compressToJpeg(rect, 100, baos))
                         {
                           image =BitmapFactory.decodeByteArray(baos.toByteArray(), 0, baos.size());
                           imageView.setImageBitmap(image);
                         }
               
                     }
               }
             }
         };

mTimer = new Timer();
         mTimerTask = new CameraTimerTask();
         mTimer.schedule(mTimerTask, 0, 500);
   }

@Override
   public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
         // TODO Auto-generated method stub
   }

@Override
   public void surfaceCreated(SurfaceHolder arg0) {
         // TODO Auto-generated method stub
         initCamera();
   }

@Override
   public void surfaceDestroyed(SurfaceHolder arg0) {
         // TODO Auto-generated method stub
         if (camera != null) {
             camera.stopPreview();
             camera.release();
             camera = null;
         }
         previewCallback = null;
         mAutoFocusCallBack = null;
   }

public void initCamera() {
         camera = Camera.open();
         Camera.Parameters parameters = camera.getParameters();
         WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); // 获取当前屏幕管理器对象
         Display display = wm.getDefaultDisplay(); // 获取屏幕信息的描述类
         parameters.setPreviewSize(display.getWidth(), display.getHeight());
         camera.setParameters(parameters);
         try {
             camera.setPreviewDisplay(surfaceHolder);
         } catch (IOException e) {
             System.out.println(e.getMessage());
         }
         camera.startPreview();
   }

class CameraTimerTask extends TimerTask {
         @Override
         public void run() {
             if (camera != null) {
               camera.autoFocus(mAutoFocusCallBack);
             }
         }
   }
}

与上一篇的简单预览相比,这篇增加了两个内容,一个是自动聚焦,一个是拍照。代码看上去很简单,没多少内容。但不亲自测试下,还会发现不少。
   刚开始在Samsung S5570 galaxy mini上测试,总是不能成功的拍照。调试跟踪后,发现自动聚焦总是失败,聚焦失败就没有进行拍照操作。后面并尝试将自动聚焦代码注释掉,直接拍照,发现也是无法显示拍照的结果。之前的PreviewCallback的代码如下:

Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);

这行代码返回的总是null,即bitmap没有成功生成。对这些代码本来就是拿来用,功能实现了,就行,对这些都只是简单的了解,当遇到bug后并百思不得其解。后来在网上几经查找发现原来是BitmapFactory.decodeByteArray只支持一定的格式,camara支持的previewformat格式为NV21,所以在获得bitmap时,需要进行转换。通过YuvImage类来转换成JPEG格式,再显示出来。

这行代码返回的总是null,即bitmap没有成功生成。对这些代码本来就是拿来用,功能实现了,就行,对这些都只是简单的了解,当遇到bug后并百思不得其解。后来在网上几经查找发现原来是BitmapFactory.decodeByteArray只支持一定的格式,camara支持的previewformat格式为NV21,所以在获得bitmap时,需要进行转换。通过YuvImage类来转换成JPEG格式,再显示出来。

CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode);//实现拍照
CameraManager.get().requestAutoFocus(this, R.id.auto_focus);//实现聚焦

首先实现拍照,再是实现聚焦,并且重载的聚焦回调函数是隔一段时间再次发出聚焦的请求,实现不断的聚焦。

public void onAutoFocus(boolean success, Camera camera) {
   if (autoFocusHandler != null) {
       Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success);
       // Simulate continuous autofocus by sending a focus request every
// AUTOFOCUS_INTERVAL_MS milliseconds.
//Log.d(TAG, "Got auto-focus callback; requesting another");
       autoFocusHandler.sendMessageDelayed(message, AUTOFOCUS_INTERVAL_MS);
       autoFocusHandler = null;
   } else {
       Log.d(TAG, "Got auto-focus callback, but no handler for it");
   }
   }

聚焦于拍照之前没有先后的逻辑关系,聚焦为了拍照更清晰。这样,关于camera取景聚焦拍照的简单过程并如上了。
    还有一个关键的点幷是回调函数。以前没有接触java代码,在看到很多接口监听处理的代码时,总是很困惑。譬如一段简单的button:

private final Button.OnClickListener addCardListener = new TextView.OnClickListener() {
@Override
public void onClick(View v) {
//在此实现button点击后的操作
}
};

如上的代码实现了点击监听,通过回调函数,当有点击操作时,并执行onClick函数。这就是一个简单的回调函数的使用。

zxing学习笔记 android入门的更多相关文章

  1. DBFlow框架的学习笔记之入门

    什么是DBFlow? dbflow是一款android高性的ORM数据库.可以使用在进行项目中有关数据库的操作.github下载源码 1.环境配置 先导入 apt plugin库到你的classpat ...

  2. MongoDB学习笔记:快速入门

    MongoDB学习笔记:快速入门   一.MongoDB 简介 MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统.在高负载的情况下,添加更多的节点,可以保证服务器性能.M ...

  3. Android动画学习笔记-Android Animation

    Android动画学习笔记-Android Animation   3.0以前,android支持两种动画模式,tween animation,frame animation,在android3.0中 ...

  4. python学习笔记--Django入门四 管理站点--二

    接上一节  python学习笔记--Django入门四 管理站点 设置字段可选 编辑Book模块在email字段上加上blank=True,指定email字段为可选,代码如下: class Autho ...

  5. WebSocket学习笔记——无痛入门

    WebSocket学习笔记——无痛入门 标签: websocket 2014-04-09 22:05 4987人阅读 评论(1) 收藏 举报  分类: 物联网学习笔记(37)  版权声明:本文为博主原 ...

  6. Mina框架的学习笔记——Android客户端的实现

    Apache MINA(Multipurpose Infrastructure for Network Applications) 是 Apache 组织一个较新的项目,它为开发高性能和高可用性的网络 ...

  7. Java学习笔记之---入门

    Java学习笔记之---入门 一. 为什么要在众多的编程语言中选择Java? java是一种纯面向对象的编程语言 java学习起来比较简单,适合初学者使用 java可以跨平台,即在Windows操作系 ...

  8. 学习笔记-----Android的View绘制过程

    边看源码边参考别人的博客等,做一下学习笔记. 要了解View的绘制,首先得知道View树的结构:(可以参考http://blog.csdn.net/qinjuning/article/details/ ...

  9. 学习笔记_J2EE_SpringMVC_01_入门

    1.    概述 笔者作为一个不太正经的不专业佛教信仰者,习惯了解事物的因果关系,所以概述就有点BBB...了.如果不喜欢这些的,请自行跳过概述章节,直接进入第二章的操作实践:2 入门示例. 1.1. ...

随机推荐

  1. entitymanager 进行数据序列化

    场景:同一个方法里,需要将前一部分执行的数据保存到数据库.后半部分读取数据时从数据库里获取,而不是获取到缓存里的数据. 理解eneityManager的这三个方法的作用和区别,首先需要分清楚Persi ...

  2. sh脚本变量赋值时同时执行命令时的环境问题

    在v2ex看到一个问题: 允许在一个命令之前立即发生一个或多个变量赋值,这些赋值为跟随着的命令更改环境变量,这个赋值的影响是暂时的. 那为什么: int=100 int=10 echo $(($int ...

  3. RobotFramework自动化4-批量操作案例【转载】

    本篇转自博客:上海-悠悠 原文地址:http://www.cnblogs.com/yoyoketang/tag/robotframework/ 前言 有时候一个页面上有多个对象需要操作,如果一个个去定 ...

  4. Laravel中ajax添加CsrfToken的方法

    //在模板文件的header头中添加 <meta name="_token" content="{{ csrf_token() }}"/> //aj ...

  5. 树莓派3b入门教程

    原文地址:传送门 这篇教程将带您一起玩转树莓派3(Raspberry Pi 3).和普通PC一样,拿到新设备第一件事就是要给它安装一个操作系统,并做一些初始化的操作.比PC简单的是,树莓派是一个固定配 ...

  6. C++之++运算符重载问题

    记录++之前先记一下左右值和存取数据的问题 数据的存放分三个部分,堆区,栈区和静态变量区 左值可以更改,右值不能更改 栈区和堆区存储的都是左值,可以随意更改其值,静态变量区部分数据是右值,比如cons ...

  7. 01Trie树【p2420】 让我们异或吧

    Description 异或是一种神奇的运算,大部分人把它总结成不进位加法. 在生活中-xor运算也很常见.比如,对于一个问题的回答,是为1,否为0.那么: (A是否是男生 )xor( B是否是男生) ...

  8. noip数论复习总结

    (上不了p站我要死了,侵权度娘背锅) 勉勉强强算是把数论复习的差不多了. 总结一下吧. 其实数论的知识大部分是结合在一起的,勉强分类总结 组合数 求法 组合数的求法根据不同情况选用不同的方法 2.3都 ...

  9. [LOJ6277]数列分块入门 1

    题目大意: 给你一个长度为$n(n\leq 50000)$的序列$A$,支持进行以下两种操作: 1.将区间$[l,r]$中所有数加上$c$: 2.询问$A_r$的值.思路: 分块. 对于整块的数据打标 ...

  10. c#ppt练习

    第六章 1.从控制台输入一个数,如果这个数大于等于60,就输出”及格”,否则输出”不及格” 从控制台输入一串字符,如果这个这串字符的长度大于3,并且字符首字母为A,,则输出“格式正确”,如果这串字符的 ...