zxing学习笔记 android入门
对于刚开始学习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入门的更多相关文章
- DBFlow框架的学习笔记之入门
什么是DBFlow? dbflow是一款android高性的ORM数据库.可以使用在进行项目中有关数据库的操作.github下载源码 1.环境配置 先导入 apt plugin库到你的classpat ...
- MongoDB学习笔记:快速入门
MongoDB学习笔记:快速入门 一.MongoDB 简介 MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统.在高负载的情况下,添加更多的节点,可以保证服务器性能.M ...
- Android动画学习笔记-Android Animation
Android动画学习笔记-Android Animation 3.0以前,android支持两种动画模式,tween animation,frame animation,在android3.0中 ...
- python学习笔记--Django入门四 管理站点--二
接上一节 python学习笔记--Django入门四 管理站点 设置字段可选 编辑Book模块在email字段上加上blank=True,指定email字段为可选,代码如下: class Autho ...
- WebSocket学习笔记——无痛入门
WebSocket学习笔记——无痛入门 标签: websocket 2014-04-09 22:05 4987人阅读 评论(1) 收藏 举报 分类: 物联网学习笔记(37) 版权声明:本文为博主原 ...
- Mina框架的学习笔记——Android客户端的实现
Apache MINA(Multipurpose Infrastructure for Network Applications) 是 Apache 组织一个较新的项目,它为开发高性能和高可用性的网络 ...
- Java学习笔记之---入门
Java学习笔记之---入门 一. 为什么要在众多的编程语言中选择Java? java是一种纯面向对象的编程语言 java学习起来比较简单,适合初学者使用 java可以跨平台,即在Windows操作系 ...
- 学习笔记-----Android的View绘制过程
边看源码边参考别人的博客等,做一下学习笔记. 要了解View的绘制,首先得知道View树的结构:(可以参考http://blog.csdn.net/qinjuning/article/details/ ...
- 学习笔记_J2EE_SpringMVC_01_入门
1. 概述 笔者作为一个不太正经的不专业佛教信仰者,习惯了解事物的因果关系,所以概述就有点BBB...了.如果不喜欢这些的,请自行跳过概述章节,直接进入第二章的操作实践:2 入门示例. 1.1. ...
随机推荐
- linux硬盘分区表为gpt
由于mbr最大支持2T不够用,给5T的新硬盘弄成GPT的 yum install -y parted#指定硬盘parted /dev/#p 查看分区#rm 1p 删除指定分区#改成gptmklabel ...
- QueryDict对象
所在的包: django.http.QueryDict HttpRequest 对象中的 GET 和 POST 属性 都是 QueryDict类型 与python字典不同:QueryDict对象一个键 ...
- Laravel5.1忽略Csrf验证的方法
在/App/Http/middleware/VerifyCsrfToken.php 文件的protected $except里面加入路由地址
- (1)powershell使用帮助
一.更新下载帮助 初始powershell是没有文档的,需要用指令更新下载到本地 管理员运行 update-help 好像有的模块需要FQ才能下载 ?? 帮助文档的开源地址 github.com/p ...
- [POI2012]A Horrible Poem
题目大意: 给定一个长度为$n(n\leq5\times10^5)$的字符串$S$,$q(q\leq2\times10^6)$组询问,每次询问子串$S_{[l_i,r_i]}$最小循环节. 思路: 若 ...
- HashMap源码-描述部分
/** * Hash table based implementation of the <tt>Map</tt> interface. This * implementati ...
- Oracle truncate、 delete、 drop区别
相同点: 1.truncate和不带where子句的delete.以及drop都会删除表内的数据. 2.drop.truncate都是DDL语句(数据定义语言),执行后会自动提交. 不同点: 1. t ...
- Request Header Or Cookie Too Large
运营反馈 Nginx 报 400 错误,具体点说:Request Header Or Cookie Too Large.其实随便搜搜就知道可以通过加大 client_header_buffer_siz ...
- quartz Cron-Expression的表达式
关于quartz Cron-Expression的表达式,这个博客写的很好 ttp://www.cnblogs.com/yaowen/p/3779284.html CronTrigger配置完整格式为 ...
- virtualenv 环境安装
# Python 2.7.6:wget http://python.org/ftp/python/2.7.6/Python-2.7.6.tar.xztar xf Python-2.7.6.tar. ...