Android 基於zxing的二維碼掃描功能的簡單實現及優化

耳東_發表於2018-07-29

由於專案中需要接入一下簡單的二維碼掃描功能,最終使用 zxing 來實現,把官方例子中的部分程式碼摘除出來做了簡單的封裝,並進行了一些優化。這裡簡單做一個記錄。

掃描二維碼

Android 中關於二維碼掃描的庫有很多,但是歸根到底無外乎下面這幾種實現方案:

其中基於以上兩者實現的比較知名的庫有:

後面這兩個開源庫做的都挺好,可定製化也挺高。不過什麼事情都要根據需求來定,就目前的需求而言用不到這麼複雜的功能,所以就自己來對官方的專案做一些改造。

整合 zxing

因為之前做二維碼掃描都是直接整合別人做好的開源方案,也沒有看過官方的專案,所以第一眼看到 zxing 官方的專案是懵逼的,不知道從哪下手。不過官方很人性化的寫了wiki——Getting Started Developing,仔細閱讀以下也挺簡單,在 Android 中使用主要有以下兩步:

  • 將需要的東西(例如 core)編譯成 jar 包或者直接從 maven 中下載
  • 編譯 android 這個模組

不過官方文件中使用的是 mvn 命令,由於沒有使用過 mvn,所以也不用按照官方的文件來做了,不過步驟大同小異。

  • 方法一

首先 clone 下需要的模組:

然後將 core 編譯成 jar 包,將 android 作為工程或者module匯入到 Android stuido 中,然後引入 jar 包就可以了。

  • 方法二

這裡還有一個更省力的辦法,只 clone android 模組,然後作為工程或者 modlue 匯入到 Android stuido 中,然後再 gradle 中新增 zxing 的依賴就行了

compile group: ‘com.google.zxing’, name: ‘core’, version: ‘3.3.2’

這個執行會提示缺少 CameraConfigurationUtils 類,這個類在 android-core 這個模組中,我的做法是直接把這個類拷貝到工程中。

然後執行專案即可,執行成功以後是一個 Android 二維碼掃描器,apk 下載

優化

官方的 demo 中功能挺多,開啟http、分享、生成二維碼等等。不過專案中用不了這麼多功能。梳理一下官方的程式碼:

  • CaptureActivity 掃描二維碼的 activity;
  • ViewfinderView 掃描框 view;
  • CameraManager 相機管理;
  • OpenCameraInterface 開啟相機的具體操作類;
  • CaptureActivityHandler 是 CaptureActivity 類中使用的 handler,主要通過他來完成訊息傳遞;
  • DecodeThread 圖片解碼執行緒;

其他的因為沒有使用到暫時沒有去管。然後根據需要把一些不必要的程式碼和邏輯刪除剩下的就是一些優化工作。

在使用中主要對他做了兩個地方的優化:

  • 增加了許可權檢查
  • 把相機的關閉和開啟放在了子執行緒中

把相機的關閉和開啟放在子執行緒中

因為相機的開啟和關閉是耗時操作,會造成主執行緒阻塞,然後開啟頁面卡頓,參考支付寶和微信在開啟的時候有一個短暫的載入框,所以這裡把相機的開啟關閉放在了子執行緒中來做。主要程式碼有下面這這些:

開啟相機

public final class OpenCameraInterface extends Thread {

    private static final String TAG = OpenCameraInterface.class.getName();

    private OpenCamera openCamera;

    private CaptureActivityHandler handler;
    // handler 用來和主執行緒通訊
    public void setHandler(CaptureActivityHandler handler) {
        this.handler = handler;
    }
    // 由於 camera 物件不能通過 handler 來傳遞,所以放在這裡通過 get 的方式來獲取。
    public OpenCamera getOpenCamera() {
        return openCamera;
    }

    private OpenCamera open() {
        int numCameras = Camera.getNumberOfCameras();
        if (numCameras == 0) {
            Log.w(TAG, "No cameras!");
            return null;
        }

        int cameraId = 0;
        while (cameraId < numCameras) {
            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
            Camera.getCameraInfo(cameraId, cameraInfo);
            if (CameraFacing.values()[cameraInfo.facing] == CameraFacing.BACK) {
                break;
            }
            cameraId++;
        }
        if (cameraId == numCameras) {
            Log.i(TAG, "No camera facing " + CameraFacing.BACK + "; returning camera #0");
            cameraId = 0;
        }

        Log.i(TAG, "Opening camera #" + cameraId);
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, cameraInfo);
        Camera camera = Camera.open(cameraId);
        if (camera == null) {
            return null;
        }
        return new OpenCamera(cameraId,
                camera,
                CameraFacing.values()[cameraInfo.facing],
                cameraInfo.orientation);
    }

    @Override
    public void run() {
        try {
            openCamera = open();
        } catch (Exception e) {
            openCamera = null;
        }
        handler.sendEmptyMessage(R.id.open_camera_complete);
    }
}

在 CameraManager 中增加一個方法。

    public void openCamera(CaptureActivityHandler handler) {
        // 這裡把開啟相機放在子執行緒中
        if (camera != null) {
            return;
        }
        threadOpen = new OpenCameraInterface();
        threadOpen.setHandler(handler);
        threadOpen.start();
    }

在 CaptureActivity 中增加一個方法在接到子執行緒發來的訊息後再初始化預覽等。這裡通過標誌位來判斷是直接進行初始化還是等待 SurfaceView 建立完成以後再進行初始化。

    public void openCameraComplete() {
        // 如果已有 SurfaceView 就可以繼續建立 否則就等待SurfaceView 建立完以後自動執行
        isCameraComplete = true;

        if (hasSurface) {
            SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
            SurfaceHolder surfaceHolder = surfaceView.getHolder();
            initCamera(surfaceHolder);
        }
    }

關閉相機

在關閉相機的時候需要增加必要的執行緒同步,否則可能造成相機未被關閉。

    public synchronized void closeDriver() {
        // 結束也是耗時操作 方在子執行緒中
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 為了防止在快速切換時出現問題,這裡等待開啟操作完成後再結束
                    threadOpen.join();
                    if (camera == null) {
                        camera = threadOpen.getOpenCamera();
                    }
                } catch (InterruptedException e) {
                    Log.e(TAG, e.toString());
                }
                camera.getCamera().release();
                camera = null;
            }
        }).start();
    }

然後就是許可權檢查這一塊了,沒什麼好說的在開啟相機之前做必要的許可權檢查就行了。需要注意的是根據官方文件,在 activity onPause 的時候需要關閉相機,然後在 onResume() 中重新開啟。而申請的彈出框會導致 activity 的 onResume 重複呼叫,針對這種情況需要做好處理。

以上就是關於 zxing 的簡單整合的所有內容,原始碼已經放在了 GitHub 上,僅供參考。commonTest-zxing

參考

版權宣告:本文為博主原創文章,轉載請宣告出處,請尊重別人的勞動成果,謝謝!

相關文章