KC Blog

【Android】用Google MLKit & Android X Camera 實作Android快速的QR code掃描應用

9 min read
AndroidDev#Android
本篇示範程式碼

若想直接看程式碼怎麼寫可以:前往程式碼解析

這個專案中使用了Kotlin、Google的MLKit跟native的AndroidX camera搭配來實作QR掃瞄器

實作效果
前言
過去我們都會使用一些第三方套件做qrcode掃瞄器

像是zxing、zbar之類的

剛好過去在專案中遇到QA反饋說目前的qrcode掃描器準確性沒那麼高

所以我去了解下並實際測試,終於知道了為何準確性沒那麼高

實際上是因為:

專案上用的是舊android.hardware.Camera相機搭配zbar去解析QR code

並以側邊的角度去或離QRcode距離較遠

導致這種的qr code scanner好像沒有ios快的感覺

所以這篇決定實做看看用google的mlkit來實作

MLkit有什麼差異呢?
- 這個 API 需要 `Android API 21` 以上 - 該API提供兩種版本

一個可以從gms動態下載module

一個使用library自帶module,如圖:

  • 我參考範例,使用新的原生相機library androidx.camera搭配使用ML Kit

    與原本zbar搭配使用android.hardware.Camera的專案比較:

    ↪ 兩個相機library都是從Android API 21以上支援

    ↪ zbar搭配使用android.hardware.Camera的版本代碼有三處workaround

    我推測是原本寫這個專案或以前範例為了在activity中去解決初始化相機問題

    所以採用下面這樣的寫法:

    ↪ 實際看了android.hardware.Camera不是thread-safe,且後面已經被@Deprecated

    ↪ 另一方面,新的 androidx.camera.core.Camera 自帶生命週期可以去處理相機週期問題

    可以避免一些奇怪的生命週期問題

✅ 所以推測換成新的androidx.camera.core.Camera可以讓自己更有效的避免生命週期的問題(因為可以自己控制)

  • 新的 androidx.camera.core.Camera 自帶生命週期可以去處理相機週期問題,可以避免一些奇怪生命週期的問題

  • 掃描器解碼使用MLkit的BarcodeScannerOptions

有提升多種可解析條碼類型

可以參考這個網址裡面有提到可支援的格式,下面為掃瞄器解碼代碼:

實作MLKit後實際測試
- 在`LG K520 Android 11`上測試
  • LG K520 Android 11上實測相機整體運行時間都沒有差太多

實際上init相機解析qr code都約1s秒

但是可避免生命週期導致的相機crash風險

  • 實測會比原本的方案,響應速度有快一點 (手機好一點的才話看不出來,體感較小)

例如zbar+ android.hardware.Camera 會完全對焦完才開始解qr code

mlkit + androidx.camera.core.Camera 對焦到一半就能開始解qr code

  • 圖片左邊為使用zbar ,右邊為使用 ML kit (此gif 降速0.25倍
  • androidx.camera.core.Camera 可以處理生命週期相關問題

新方案放慢0.25倍看,體感上會變成:先等一陣子才顯示相機(圖右)

  • 不過提升比較有感的是,當你用奇怪的角度 或是 相機距離比較遠 會比較容易掃描到
對新版相機android.hardware.Camera在app中進行調教
- 建立ImageAnalysis時加入目標解析度設置`.setTargetResolution(Size(screenResolutionForCamera.x, screenResolutionForCamera.y))` - 其中 screenResolutionForCamera 是拿螢幕解析度去設置相機目標解析度

有設置相機目標解析度與否的差異在 相機在離得遠的掃成功機率較高

  • 另外補充:其中該api java doc文件有提到
    1. 設定TargetResolution的相機預設解析度是640*480
    2. 設定後也會有優先順序去選擇實際的Resolution
    3. 這邊是大綱:優先選擇接近設定且大於的的Target Resolution的 > 優先選擇接近設定且小於的Target Resolution 的>其中分辨率的比例會由提供的Size優先決定
    4. 因此推測,有可能因為硬體限制導致每台能解析的遠近程度有所差異
結果
- 經過上面的這些調整與優化,再請QA同仁幫忙測試的時候

他反饋跟以前提供的app的準確度確實提升了

細問之後他說確實從側邊離比較遠的地方掃描成功率被偵測到的機率更高了 至此...解決了一個問題XD

程式碼開發教學
- 感謝大家看完上面的分析

我在本篇最上面放了source code

有興趣的可以直接clone下來去改

  • 下面我將分享主要的QR code掃瞄器的程式碼解析

有興趣的可以參考

BaseCameraLifecycleObserver

程式碼解說

BaseCameraLifecycleObserver這個類主要是用來讓其他繼承

有些基本的相機init的內容

也處理了一些生命週期相關的內容

(e.g.對應週期相機的處理)

也開放了對外method可以手動開啟或關閉相機

cameraProviderDeferred

比較值得一提的是,這邊用了一個cameraProviderDeferred

用來取得ProcessCameraProvider

因為該行為較為耗時 所以使用異步操作來做較為靈活

(不過我也有試過直接實例化ProcessCameraProvider

大多數情況不容易遇到一些ANR或卡住的情況

只有偶發1、2次在比較舊的機型,如Android 5.0)

這邊的概念就是

CompletableDeferred其中用了.apply

在apply範圍內會去創建ProcessCameraProvider

在需要使用的地方使用await()去等待

同時達成了異步操作也能等待實際創建了再去操作

其中completableDeferred跟一般Deferred的差異在於

他可以用complete()去手動控制完成時機

剛好適用本例

需要等待ProcessCameraProvider listener返回

才能得知初始化完成

ScannerLifecycleObserver

程式碼解說

ScannerLifecycleObserver就比較單純了

繼承了BaseCameraLifecycleObserver

主要是寫一些可能根據專案需求才需要的feature

像是一些擴充的內容

getCameraDisplayOrientation

可以用來調整相機支援的解析度

或getImageAnalysis拿到ImageAnalysis

ImageAnalysis主要是MLKit中用來設定可以解析哪些條碼類型

ScannerAnalyzer

程式碼解說

ScannerAnalyzer則是拿來針對條碼做解析的實作

繼承了BaseCameraLifecycleObserver

因為我們是要解QR code所以用了FORMAT_QR_CODE