iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
Software Development

從 Fragment 到 Compose — 老 Android App 重寫的架構取捨系列 第 24 篇

Day 24|13 個掃描 Activity 收成一個 ScannerScreen:ML Kit + CameraX

  • 分享至 

  • xImage
  •  

動筆前先答(兩題)

1.【預測再驗證|平台能力】 新的掃描畫面用 CameraX 的 ImageAnalysis 餵影格給 ML Kit 的條碼辨識,骨架大概是這樣:

val analysis = ImageAnalysis.Builder()
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()
val scanner = BarcodeScanning.getClient(
    BarcodeScannerOptions.Builder()
        .setBarcodeFormats(Barcode.FORMAT_CODE_128, Barcode.FORMAT_QR_CODE)
        .build(),
)
analysis.setAnalyzer(executor) { imageProxy ->
    val image = InputImage.fromMediaImage(imageProxy.image!!, imageProxy.imageInfo.rotationDegrees)
    scanner.process(image)
        .addOnSuccessListener { barcodes -> onBarcodes(barcodes.map { it.rawValue }) }
        .addOnCompleteListener { imageProxy.close() }
}

下面每個情境,先寫下你的預測,再拿實機(印幾張 Code 128 與 QR Code)或 instrumented test 驗證:

  1. 一張條碼穩穩放在鏡頭前兩秒。onBarcodes 被呼叫幾次?每次的清單裡有幾筆?如果呼叫端把每次呼叫都當成「掃到一個新條碼」,畫面上的清單會長成什麼樣?你打算在哪一層去重:analyzer、掃描畫面的 ViewModel,還是呼叫端?去重的依據是「值相同」還是「值相同且在 N 秒內」?
  2. 兩張不同的條碼同時在畫面裡。清單裡兩筆的順序由什麼決定?連續幾個影格之間順序會不會變?「只要一個結果」的掃描模式該取第一筆、取最大的、還是要使用者點選?舊 App 的做法是讓使用者點畫面上的框來挑。
  3. 把 rotationDegrees 固定傳 0,手機直拿掃一張橫向印的 Code 128,再掃一張 QR Code。哪一種還掃得到?為什麼兩種不一樣?
  4. 把 imageProxy.close() 那行拿掉。預覽畫面會怎樣?onBarcodes 還會被呼叫嗎?幾次之後停?
  5. 把 setBarcodeFormats 拿掉改成辨識全部格式。同一張條碼從進畫面到第一次回傳的時間差多少?用 System.nanoTime() 量十次取中位數。差距值得為此把格式寫進設定嗎?

最後回答:這五個情境裡,哪幾個可以用 instrumented test 餵固定圖片(InputImage.fromBitmap)穩定重現?哪幾個只能在實機驗、而且要寫進發版前的手動檢查清單?

我的回答:(待補)

2.【決策邊界|架構論述】 舊 App 有 13 個掃描 Activity,每個大約一千行、九成五相同:相機與辨識、閃光燈開關、掃到的條碼堆成一個列表、「清除」與「關閉」鈕、關閉時把列表當結果回傳。不同的是那 5%:有的掃一個就結束、有的連續掃;有的每掃到一筆就立刻呼叫後端驗證這筆能不能收,後端拒絕就跳對話框、震動、不加進列表;有的接受掃到的條碼比業務編號長、只取前面固定長度的那段;有的要帶上「這次是為哪個客戶、哪種任務掃的」。

新設計要收成一個 ScannerScreen,差異由 ScanConfig 描述、結果用 ScanResult 交回。請寫下:

  • ScanConfig 裡放什麼、不放什麼?至少判斷這幾項各歸哪邊(設定資料、呼叫端的回呼、還是乾脆不屬於掃描畫面):接受的條碼格式;單次或連續;去重規則;掃到的值要不要照業務規則修剪或檢查長度;每掃一筆就向後端驗證並決定收不收;驗證失敗時的提示文字;預設開不開閃光燈;「為哪個客戶掃」這類只有呼叫端在乎的上下文。
  • 「每掃一筆就向後端驗證」是最難收的那 5%。三種做法:(A) 掃描畫面只回報「掃到了」,呼叫端自己驗證,驗證結果再透過狀態回到掃描畫面顯示;(B) ScanConfig 帶一個 suspend (String) -> Verdict 的驗證函式,由掃描畫面呼叫並顯示結果;(C) 不做通用畫面,改成提供「相機預覽+辨識」的可組合元件,13 種流程各自組自己的畫面。每種做法在這幾個條件下會怎樣:驗證要打網路而手機離線;驗證要兩秒而使用者已經把鏡頭移到下一個條碼;之後要新增第 14 種流程;要為掃描畫面寫單元測試。你預設選哪一個,什麼條件下改選別的?
  • 掃到的條碼比業務編號長時,舊 App 直接截掉後面。「截不截、照什麼規則截」這個知識該住在 core:scanner、data 的 mapper,還是呼叫端的 ViewModel?寫下你的理由,以及這個決定對第 1 題「去重的依據」有什麼影響。
  • 手動輸入:鏡頭壞了或條碼磨損時,使用者要能用鍵盤輸入編號。手動輸入是 ScannerScreen 的一部分(同一個畫面切換模式),還是呼叫端自己的另一個輸入框?兩種做法下 ScanResult 要不要區分「掃的」和「打的」?

結果怎麼從掃描畫面回到呼叫端(SavedStateHandle 還是共享 ViewModel)是明天 Day 25 的題目,今天不用答。

我的回答:(待補)


上一篇
Day 23|端到端:拍照 → 佇列 → 上傳 → 進度
系列文
從 Fragment 到 Compose — 老 Android App 重寫的架構取捨 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言