天天看點

用Camera的預覽和zXing來寫一個二維碼掃描功能

  首先我這裡不會再介紹Camera的基本屬性了解介紹了,也不會講預覽的基本設定。會直奔主題将怎麼寫二維碼掃描的界面的自定義控件和掃描過程實作以及裡面。至于Camera的基本介紹請看我之前的部落格:Camera基本介紹,用Camera寫一個自己的基本預覽界面。這兩篇部落格是這個二維碼掃描的基礎篇。

(1)聲明自己要解析那些類型的,必須解析二維碼、解析條形碼。

(2)開啟一個線程去讀取預覽界面的指定區域的資料源(byte[])

(3)利用zXing的方法去先判斷擷取到的資料源裡面的點是否是有效的,再在有效的基礎上結合(1)聲明的格式去解析這些有用的點,直到解析成功、失敗、主動或被動停止為止。

(4)掃描過程從始至終都會有自定義控件和動畫跟随着,是以這個(4)是貫穿整個過程并且受到(3)的控制。

(5)回調(3)執行結果來控制(4);

   下面我對上面的四點進行分别解析:

(1)聲明解析類型:

import android.content.Intent;
import android.net.Uri;

import com.google.zxing.BarcodeFormat;

import java.util.Arrays;
import java.util.List;
import java.util.Vector;
import java.util.regex.Pattern;

public class DecodeFormatManager {
    private static final Pattern COMMA_PATTERN = Pattern.compile(",");

    static final Vector<BarcodeFormat> PRODUCT_FORMATS;
    static final Vector<BarcodeFormat> ONE_D_FORMATS;
    static final Vector<BarcodeFormat> QR_CODE_FORMATS;
    static final Vector<BarcodeFormat> DATA_MATRIX_FORMATS;

    static {
        PRODUCT_FORMATS = new Vector<>(5);
        PRODUCT_FORMATS.add(BarcodeFormat.UPC_A);
        PRODUCT_FORMATS.add(BarcodeFormat.UPC_E);
        PRODUCT_FORMATS.add(BarcodeFormat.EAN_13);
        PRODUCT_FORMATS.add(BarcodeFormat.EAN_8);

        ONE_D_FORMATS = new Vector<>(PRODUCT_FORMATS.size() + 4);
        ONE_D_FORMATS.addAll(PRODUCT_FORMATS);
        ONE_D_FORMATS.add(BarcodeFormat.CODE_39);
        ONE_D_FORMATS.add(BarcodeFormat.CODE_93);
        ONE_D_FORMATS.add(BarcodeFormat.CODE_128);
        ONE_D_FORMATS.add(BarcodeFormat.ITF);

        QR_CODE_FORMATS = new Vector<>(1);
        QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE);

        DATA_MATRIX_FORMATS = new Vector<>(1);
        DATA_MATRIX_FORMATS.add(BarcodeFormat.DATA_MATRIX);

    }

    private DecodeFormatManager() {

    }

    private static final class Holder {
        private static final DecodeFormatManager DECODE_FORMAT_MANAGER = new DecodeFormatManager();
    }

    public static DecodeFormatManager getInstance() {
        return Holder.DECODE_FORMAT_MANAGER;
    }

    static Vector<BarcodeFormat> parseDecodeFormats(Intent intent) {
        List<String> scanFormats = null;
        String scanFormatsString = intent.getStringExtra(Intents.Scan.SCAN_FORMATS);
        if (scanFormatsString != null) {
            scanFormats = Arrays.asList(COMMA_PATTERN.split(scanFormatsString));
        }
        return parseDecodeFormats(scanFormats, intent.getStringExtra(Intents.Scan.MODE));
    }

    static Vector<BarcodeFormat> parseDecodeFormats(Uri inputUri) {
        List<String> formats = inputUri.getQueryParameters(Intents.Scan.SCAN_FORMATS);
        if (formats != null && formats.size() == 1 && formats.get(0) != null) {
            formats = Arrays.asList(COMMA_PATTERN.split(formats.get(0)));
        }
        return parseDecodeFormats(formats, inputUri.getQueryParameter(Intents.Scan.MODE));
    }

    private static Vector<BarcodeFormat> parseDecodeFormats(Iterable<String> scanFormats,
                                                            String decodeMode) {
        if (scanFormats != null) {
            Vector<BarcodeFormat> formats = new Vector<BarcodeFormat>();
            try {
                for (String format : scanFormats) {
                    formats.add(BarcodeFormat.valueOf(format));
                }
                return formats;
            } catch (IllegalArgumentException iae) {
                // ignore it then
            }
        }
        if (decodeMode != null) {
            if (Intents.Scan.PRODUCT_MODE.equals(decodeMode)) {
                return PRODUCT_FORMATS;
            }
            if (Intents.Scan.QR_CODE_MODE.equals(decodeMode)) {
                return QR_CODE_FORMATS;
            }
            if (Intents.Scan.DATA_MATRIX_MODE.equals(decodeMode)) {
                return DATA_MATRIX_FORMATS;
            }
            if (Intents.Scan.ONE_D_MODE.equals(decodeMode)) {
                return ONE_D_FORMATS;
            }
        }
        return null;
    }

}
           

(2和3)開啟線程去讀取預覽界面的指定區域的資料源,然後線上程裡面使用handler來不斷的讀取和解析資料:

import android.app.Activity;
import android.os.Handler;
import android.os.Looper;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import com.google.zxing.ResultPointCallback;

import java.util.Hashtable;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;

public class DecodeThread extends Thread {

    private Activity activity;
    private final CountDownLatch handlerInitLatch;
    private DecodeHandler handler;
    private final Hashtable<DecodeHintType, Object> hints;
    public static final String BARCODE_BITMAP = "barcode_bitmap";

    public DecodeThread(Activity activity, Vector<BarcodeFormat> decodeFormats,
                        String characterSet, ResultPointCallback resultPointCallback) {
        this.activity = activity;
        handlerInitLatch = new CountDownLatch(1);
        //Decode Hint Type 解碼提示類型
        hints = new Hashtable<>(3);//哈希表集合

        if (decodeFormats == null || decodeFormats.isEmpty()) {
            decodeFormats = new Vector<>();
            decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);
            decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
            decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
        }

        hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);

        if (characterSet != null) {
            hints.put(DecodeHintType.CHARACTER_SET, characterSet);
        }

        hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
    }

    Handler getHandler() {
        try {
            handlerInitLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return handler;
    }

    @Override
    public void run() {
        super.run();
        Looper.prepare();
        handler = new DecodeHandler(activity, hints);
        handlerInitLatch.countDown();
        Looper.loop();
    }
}
           
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;

import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;

import java.util.Hashtable;

import shen.da.ye.myscandemo.R;
import shen.da.ye.myscandemo.ScanActivity;
import shen.da.ye.myscandemo.camera.CameraManager;
import shen.da.ye.myscandemo.camera.PlanarYUVLuminanceSource;

public class DecodeHandler extends Handler {

    private static final String TAG = "cy==DecodeHandler";
    private final MultiFormatReader MULITFORMATREADER;
    private Activity activity;

    public DecodeHandler(Activity activity, Hashtable<DecodeHintType, Object> hints) {
        MULITFORMATREADER = new MultiFormatReader();
        MULITFORMATREADER.setHints(hints);
        this.activity = activity;
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case R.id.decode:
                decode(((byte[]) msg.obj), msg.arg1, msg.arg2);
                break;
            case R.id.quit:
                Looper.myLooper().quit();
            default:
                break;
        }
    }

    /**
     * 解碼取景器矩形中的資料,以及花費多長時間。 為了效率,将相同的讀取對象從一個解碼重用到下一個解碼。
     *
     * @param data   The YUV preview frame.
     * @param width  The width of the preview frame.
     * @param height The height of the preview frame.
     */
    private void decode(byte[] data, int width, int height) {
        //解析從預覽界面的矩形中的資料
        long start = System.currentTimeMillis();
        Result rawResult = null;
        byte[] rotateData = new byte[data.length];
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                rotateData[x * height + height - y - 1] = data[x + y * width];
            }
        }
        int tmp = width;
        width = height;
        height = tmp;

        //二維碼掃描核心處理代碼是在這裡調用zXing
        PlanarYUVLuminanceSource planarYUVLuminanceSource = CameraManager.getInstance().buildLuminanceSource(rotateData, width, height);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(planarYUVLuminanceSource));
        try {
            rawResult = MULITFORMATREADER.decodeWithState(bitmap);
        } catch (NotFoundException e) {
            e.printStackTrace();
        } finally {
            MULITFORMATREADER.reset();
        }

        if (rawResult != null) {
            long end = System.currentTimeMillis();
            Message message = Message.obtain(((ScanActivity) activity).getHandler(), R.id.decode_succeeded, rawResult);
            Bundle bundle = new Bundle();
            bundle.putParcelable(DecodeThread.BARCODE_BITMAP, planarYUVLuminanceSource.renderCroppedGreyScaleBitmap());
            message.setData(bundle);
            message.sendToTarget();
        } else {
            Message message = Message.obtain(((ScanActivity) activity).getHandler(), R.id.decode_failed);
            message.sendToTarget();
        }

    }
}
           

(5)再用一個hander來解決線程裡面讀取資料的資訊回調:

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;

import java.util.Vector;

import shen.da.ye.myscandemo.R;
import shen.da.ye.myscandemo.ScanActivity;
import shen.da.ye.myscandemo.camera.CameraManager;

public class CaptureActivityHandler extends Handler {

    private static final String TAG = "cy====CaptureHandler";
    private Activity activity;
    private State mState;
    private final DecodeThread decodeThread;

    private enum State {
        PREVIEW,
        SUCCESS,
        DONE
    }

    /*Vector:向量,Barcode Format:條碼格式*/
    public CaptureActivityHandler(Activity activity, Vector<BarcodeFormat> barcodeFormats,
                                  String characterSet) {
        this.activity = activity;
        decodeThread = new DecodeThread(activity, barcodeFormats, characterSet,
                new ViewfinderResultPointCallback(((ScanActivity) activity).getViewfinderView()));
        decodeThread.start();
        mState = State.SUCCESS;

        //(1)開始預覽
        CameraManager.getInstance().startPreview();
        //(2)開始解碼
        restartDecode();
    }

    private void restartDecode() {
        if (mState == State.SUCCESS) {
            mState = State.PREVIEW;
            CameraManager.getInstance().requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
            CameraManager.getInstance().requestAutoFocus(this, R.id.auto_focus);
            ((ScanActivity) activity).drawViewfinder();
        }
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case R.id.auto_focus:
                //接收到自動定焦msg
                if (mState == State.PREVIEW) {
                    CameraManager.getInstance().requestAutoFocus(this, R.id.auto_focus);
                    Log.i(TAG, "自動定焦");
                }
                break;
            case R.id.restart_preview:
                //接收到重新啟動預覽的msg,是在掃描走了onPause之後再走onResume的時候調用這個方法
                restartDecode();//TODO 看一下這個需不需要再重新開始預覽,加一個跳轉界面測試一下
                break;
            case R.id.decode_succeeded:
                //接收到解碼成功的msg
                mState = State.SUCCESS;
                Bundle bundle = msg.getData();
                Bitmap barcode = bundle == null ? null : (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);
                ((ScanActivity) activity).handleDecode((Result) msg.obj, barcode);
                Log.i(TAG, "解析掃描到捕捉的點成功");
                break;
            case R.id.decode_failed:
                //接收到掃描失敗的msg,這個掃描失敗是捕捉到的東西不是一個正常的條形碼和二維碼,那麼繼續捕捉。還有一個掃描時失敗是捕捉到的點是二維碼,但是從裡面解析到的資料失敗
                mState = State.PREVIEW;
                CameraManager.getInstance().requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
                Log.i(TAG, "捕捉的點經過解析是無效的,重新再捕捉");
                break;
            case R.id.return_scan_result:
                //接收到
                activity.setResult(Activity.RESULT_OK, ((Intent) msg.obj));
                activity.finish();
                break;
            case R.id.launch_product_query:
                //接收到
                String url = (String) msg.obj;
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
                activity.startActivity(intent);
                break;
            default:
                break;
        }
    }

    //onPause的時候停止掃描
    public void quitSynchronously() {
        mState = State.DONE;
        CameraManager.getInstance().stopPreview();
        Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit);
        quit.sendToTarget();
        try {
            decodeThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //移除到正在發送的資訊,保證現在沒有任何排隊的資訊
        removeMessages(R.id.decode_succeeded);
        removeMessages(R.id.decode_failed);
    }
}
           

源代碼