iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
Software Development

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

Day 17|硬體抽象:把藍牙磅秤包成 Flow

  • 分享至 

  • xImage
  •  

動筆前先答(兩題)

1.【模擬面試追問|平台能力】 舊 App 讀 BLE 廣播式磅秤的方式,是用 Handler 排程:開始掃描、10 秒後停止、再重新開始,掃到的讀值透過 callback 交給畫面。新版打算改成:

interface ScaleDevice {
    val readings: Flow<WeightReading>
}

// 實作用 callbackFlow 包 BLE 掃描:開始收集就開始掃,停止收集就停止掃

面試官順著這個設計往下追問:

  • 如果 callbackFlow 裡忘了寫 awaitClose { ... },或寫了但沒有在裡面停止掃描,分別會發生什麼事?
  • 秤重畫面和一個「磅秤連線狀態」的小元件同時收集 readings,會開兩次掃描還是一次?如果你希望只掃一次,要怎麼做?兩者都離開畫面後,掃描應該立刻停,還是等幾秒?為什麼?
  • 使用者把 App 切到背景,秤重畫面還在 back stack 裡。掃描會繼續嗎?你希望它繼續嗎?

不看大綱,寫下你會怎麼回答。

我的回答:

  1. 資源未適時(符合元件生命週期)釋放,會發生記憶體洩漏。
  2. 我不知道是兩次還一次;如果只掃一次,會要從監聽器下手;兩者都離開畫面後,我覺得應該立刻停止。
  3. 我不清楚。

2.【預測再驗證|測試與品質】 為了不靠真的磅秤測試 ViewModel,你寫了一個假的裝置,用 callbackFlow 模擬「磅秤在很短時間內送出大量讀值」:

class FakeScaleDevice : ScaleDevice {
    override val readings: Flow<WeightReading> = callbackFlow {
        repeat(1_000) { i -> trySend(WeightReading(grams = i)) }
        awaitClose { }
    }
}

@Test
fun `a slow collector`() = runTest {
    val received = mutableListOf<Int>()
    val job = launch {
        FakeScaleDevice().readings.collect {
            delay(10)                // 模擬畫面處理得比較慢
            received += it.grams
        }
    }
    advanceUntilIdle()
    job.cancel()
    println("收到 ${received.size} 筆,最後一筆是 ${received.lastOrNull()}")
}

收集端收到幾筆?最後一筆是多少?先寫下預測與理由,再跑測試驗證。接著把 callbackFlow { ... } 後面分別加上 .conflate()、.buffer(Channel.UNLIMITED),再各預測一次。

最後判斷:對秤重這件事來說,「中間的讀值掉了」可以接受嗎?你會選哪一種寫法?如果你的測試只送 3 筆讀值,這三種寫法的差別還測得出來嗎?

我的回答:
(待補)


上一篇
Day 16|Use case 層要不要:排序與篩選規則該放哪裡
系列文
從 Fragment 到 Compose — 老 Android App 重寫的架構取捨 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言