android入门:zxing学习笔记

android入门:zxing学习笔记(一)

对于刚开始学习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,实现了扫描时的界面,不停的刷新,并将识别的一些数据,如定位的点回调显示在界面上。

android入门:zxing学习笔记(二)

上一篇介绍了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();
      }
    }
  }

如果想深入的查看view刷新的过程,具体实现,查看下面这个链接,这个系列文章写的很详细。

   AndroidBluetooth博客:View编程(2): invalidate()再探

android入门:zxing学习笔记(三)

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接口,并重写了这个接口的三个方法。

    关于对surfaceHolder,surfaceView,SurfaceHolder.Callback的介绍,请看这里,很详细哦。
    surfaceview总之能够获相机硬件捕捉到的数据并显示出来,在上面的代码中,先初始化了surfaceholder对象。并重写了surfaceCreated函数,在这个函数中,完成了对相机打开取景的基本操作。首先是Camera.open()获取一个Camera对象,在初始化一些camera参数,如图像格式,图像预览大小,刷新率等等。在设置预览显示,最后别忘了startPreview,则完成了取景。由于刚开始开发的工程需要将相机的取景设置为竖屏的,Barcode Scanner设置的是横屏的,开始再尝试调整图片显示方向时,我以为是再manifest中重新设置,
android:screenOrientation="landscape"
      将landscape该为portrait,结果却很意外,屏幕是竖着显示了,但是取景后的内容与显示却是横竖相反的,手机竖着取景,显示的却是横着的。不可以简单的通过调整这个参数值来改变方向。后面调用下面这个函数,重新设置了预览照片的显示方向。
camera.setDisplayOrientation(90);
     调整显示方向后,取景终于正常了。但是在后面预览拍照结果时,发现这都是假象,相机底层取景还是横屏的,只是在预览时进行了方向调整,这样还存在一个显示照片拉伸的问题。这个没有深入查看了。
     可以看这篇文章,Android Camera小结,写得比我的更全面,实用。
http://www.diybl.com/course/3_program/java/android/20111201/563696.html  

android入门:zxing学习笔记(四)

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格式,再显示出来。 具体讨论,请点这里。
解决照片的显示问题后,还有一个问题便是自动聚焦失败。上面特意强调了使用的事HTC 的手机测试成功,是因为之前在samsung s5570 上测试总是失败,拿到HTC的那款手机上立马成功。应该是三星的这款手机不支持自动聚焦。之前在这个三星手机上跑过Barcode Scanner,就自以为这手机能够自动聚焦,并一直在查找自己代码的原因。后面在仔细的读了Barcode Scanner的代码后,发现他得处理方式是:
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函数。这就是一个简单的回调函数的使用。
  关于回调函数请看这里,还有 回调函数在android中得体现点这里
    大家的分享方便你我。

android入门:zxing学习笔记(五)

莫道君行早,更有早行人。     

     Barcode Scanner不只是上面两篇说的这么简单,还有其他处理,如闪光灯,放大处理,最优的预览尺寸等等。这些不影响对代码的理解,知道camera的使用后,开始看看Barcode Scanner是如何高效的进行识别处理的。在android文件夹下,就有一个thread类:DecodeThread,两个handler类:CaptureActivityHandler和DecodeHandler。在没有认真看两个类时,一直以为这两个handler是理所当然的方式,以为自己就知道了。后面发现不是这么简单,尤其是DecodeThread的实现是android 中工作线程的经典实现。

     在Barcode Scanner中,有着繁多的消息传送,处理的消息的handler也有两个。如何将消息传送到指定的handler中去,从代码上看挺清晰的,将message和指定的handler绑定。前面提到过,在android中有主线程和工作线程之分。Activity这类的main thread,还有自己创建的work thread,如果要更新UI,则需要再main thread中进行处理,可以通过handler来实现消息传递,将消息送到main thread的消息队列去。

     Barcode Scanner中有两个Thread 和 两个handler,在这四个类中通过加入这样的语句判断他们thread中得关系:
System.out.println(TAG + "The worker thread id = " +   Thread.currentThread().getId()); //判断线程ID
最后运行的结果:
01-12 02:41:12.594: I/System.out(655): CaptureActivity The main thread id = 1
01-12 02:41:14.605: I/System.out(655): CaptureActivityHandler The handler thread id = 1
01-12 02:41:12.946: I/System.out(655): DecodeThread The worker thread id = 13
01-12 02:41:13.094: I/System.out(655): DecodeHandler The handler thread id = 13

由此可见,这两个handler都分别属于他们的thread。但在创建这两个handler时,有很大的差别。CaptureActivityHandler的创建只是简简单单的new了,没有其他辅助。这就是main activity在创建时,系统默认为它创建一个looper,负责管理该线程的消息循环,取送消息等,不需要额外指定。但对于自己创建的Thread,系统默认是没有为其创建looper的,需要自己为它创建消息循环。

    先看下DecodeThread的代码 : 去掉了跟理解线程不相关的代码。

final class DecodeThread extends Thread {
  public static String TAG = DecodeThread.class.getSimpleName();
  private final CaptureActivity activity;
  private Handler handler;
  private final CountDownLatch handlerInitLatch;//到计数的锁
  DecodeThread(CaptureActivity activity,
               Vector<BarcodeFormat> decodeFormats,
               String characterSet,
               ResultPointCallback resultPointCallback) {

    this.activity = activity;
    handlerInitLatch = new CountDownLatch(1);//从1开始到计数
    
  }

  Handler getHandler() {
    try {
      handlerInitLatch.await();//阻塞先等handler被初始化了才能返回结果。改计数锁即等countdown-->0。
    } catch (InterruptedException ie) {
      // continue?
    }
    return handler;
  }

  @Override
  public void run() {
    Looper.prepare();
    handler = new DecodeHandler(activity, hints);
    handlerInitLatch.countDown();//启动到计数,countdown-1 变成0;
    System.out.println(TAG + "The worker thread id = " +   Thread.currentThread().getId()); //判断线程ID
    Looper.loop();
  }

前面定义了一个CountDownLatch类型变量,该变量为一个倒计数用的锁。用法挺简单,如代码中,先new CountDownLatch(1),计数值为1, handlerInitLatch.countDown(), 开始倒数。handlerInitLatch.await() 若计数值没有变为0,则一直阻塞。直到计数值为0后,才return handler,因此在调用getHandler时不会返回null的handler。

    在创建DecodeThread线程的handler时,首先在线程中调用Looper.prepare()来创建消息队列,再创建附于该线程的handler对象,最后调用Looper.loop()进入消息循环,这个这个loop()循环不会立马返回,需要自己主动调用Looper.myLooper().quit()才会返回。这就是自己创建一个工作线程,为其分配一个消息队列,消息循环的简单迅速办法。

    前面说到camera的自动聚焦,只是隔断时间定期的不停向CaptureActivityHandler发送自动聚焦请求,这就是一个消息传送:

Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success);
autoFocusHandler.sendMessageDelayed(message, AUTOFOCUS_INTERVAL_MS);
这样创建了消息,隔断时间发送。

 关于Android的消息处理机制,这里有篇更好的文章,请点这里还有这一篇介绍looper的。这两篇说的更透彻,实用。这还发现一篇巨作。

android入门:zxing学习笔记(六)

我希望你骑着摩托车离开这里,沿着这条河一直到大海边。

     已经连续写了五篇zxing的学习笔记了,刚开始写的时候,只是想简简单单的记录下自己在学习android过程积累的点滴,却没想到写着写着变成了好像在向某人诉说自己一点浅薄的理解似的。回头来看这些稚嫩的笔记,发现思维逻辑有点混乱,讲述的杂乱无章,没达要点。不求全面,但求透彻。在写这些随笔的过程,又更多的理解了Barcode scanner的设计,弄懂了之前很多的一知半解,尤其是理所当然的潜在错误认知,所谓的眼高手低。View,Camera,thread,looper我都再次认真的查阅资料,谨慎的看了多遍,当心自己写错,新年我愿慢慢进步。

     言归正传。

     在使用Barcode Scanner扫描GB2312编码的qr码,扫描结果会出现乱码,无法正常显示。之所以会出现这个问题,我通过跟踪调试后找到在DecodedBitStreamParser类里面完成了对原始的bytes进行了解析,这个代码有很多没明白,通过打日志知道了代码运行的轨迹。通过对二十多张不同编码方式的qr图扫描分析的结果,发现大部分都通过调用decodeByteSegment进行解析,其中会调用函数StringUtils.guessEncoding(byte[] bytes, Hashtable hints)来对编码方式进行猜测,hits中可以指定使用的编码方式,如果没有指定则猜测。 
// For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS,
// which should be by far the most common encodings. ISO-8859-1
// should not have bytes in the 0x80 - 0x9F range, while Shift_JIS
// uses this as a first byte of a two-byte character. If we see this
// followed by a valid second byte in Shift_JIS, assume it is Shift_JIS.
// If we see something else in that second byte, we'll make the risky guess
// that it's UTF-8.
 这部分是代码中对编码方式猜测的基本方法。缺少对GB2312的猜测。GB2312使用两个字节来进行编码。第一个字节的范围在(0xB0,0xF7),紧接着的第二个字节的范围在(0xA0,0xF7)。根据GB2312的这个编码规则,可以进行一个简单的判断,解决扫描GB2312编码的qr图乱码问题。
for (int i = 0; i < length; i++) {
   int value = bytes[i] & 0xFF;
   if (value > 0x7F)// 如果大于127,则可能是GB2312,就开始判断该字节,和下一个字节
   {
       if (value > 0xB0 && value <= 0xF7)// 第一个字节再此范围内,则开始判断第二个自己
       {
           int value2 = bytes[i + 1] & 0xFF;
           if (value2 > 0xA0 && value2 <= 0xF7) 
           {
               return true;
           }
        }
  }
}
以上是一个简单的判断,通过加入到guesscoding中后可以正确的识别出GB2312编码的qr图。但感觉这样的方法还是比较低效率的,没有真正融合到zxing的源码中。需要再一步的思考。

      前面有一篇关于camera旋转的问题,如果将拍照转为竖屏,除了在manifest中指定该activity的方向外,还需要camera.setDisplayOrientation(90),调整预览时的方向。但是camera取的数据还是原来的横屏的朝向,如果在对图片进行分析前没有对图片做一个旋转的操作,则图片最后保存下来,会发现它是横的,并且那些ResultPoint也无法正确的标记出qr图上的特征点。 今天看到一个帖子,解决了这个问题,看这个帖子请点这里。

      其中最关键的代码是再DecodeHandler.java中得decode函数调用buildLuminanceSource函数前进行如下的一个旋转。
byte[] rotatedData = new byte[data.length];
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++)
            rotatedData[x * height + height - y - 1] = data[x + y * width];
    }
 这样就解决了ResultPoint点标记的不正确的问题,当然除了设置方向,旋转数据,还需要调整下view的布局,使扫描框看上去更协调。

原文地址:http://www.cnblogs.com/liuan/


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值