对于刚开始学习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. python enumerate元素的时候可以获取下标,并且可以指定开始的下标值。

    list=["a","b","c","d","e"] for i,item in enumerate ...

  2. ubuntu安装ftp server并匿名访问

    $ sudo apt install vsftpd //修改添加以下配置 $ sudo vim /etc/vsftpd.conf #listen_ipv6=YES #注销ipv6监听 listen=Y ...

  3. mysql 事务、游标

    mysql 事务 在MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务,所以很多情况下我们都使用innodb引擎. 事务处理可以用来维护数据库的完整性,保证成批的SQL语句要么全部执行 ...

  4. mysql故障(主从复制sql线程不运行)

    故障现象: 进入slave服务器,运行: mysql> show slave status\G ....... Relay_Log_File: localhost Relay_Log_Pos: ...

  5. 查看 Laravel 的 SQL 语句的方法

    在使用 Laravel 的 Eloquent 进行数据查询的时候,很多小伙伴都想看到背后执行的 SQL 语句到底是什么样的,这小笔录就是解决这个小问题的: 在 Providers/AppService ...

  6. python 字符串最长公共前缀

      编写一个函数来查找字符串数组中的最长公共前缀. 如果不存在公共前缀,返回空字符串 "". 示例 1: 输入: ["flower","flow&qu ...

  7. Codeforces 1027F. Session in BSU

    题目直通车:Codeforces 1027F. Session in BSU 思路: 对第一门考试,使用前一个时间,做标记,表示该时间已经用过,并让第一个时间指向第二个时间,表示,若之后的考试时间和当 ...

  8. Maven笔记:

    启动tomcat的时候报这样的错误:java.lang.ClassNotFoundException: org.springframework.web.filter.CharacterEncoding ...

  9. jcl sort comp3 to 表示型

    Lets say your packed data is at 10th column and is of length 6, S9(4)V99 You could try the following ...

  10. 集合框架(上):学生选课(collection)

    利用集合存储课程信息: 1.Course类 package com.collection; public class Course { public String id; public String ...