用 Android Service 實作一個 Timer

Kash Yang
6 min readOct 23, 2023

--

Photo by Daria Shevtsova on Unsplash

延續前一章:如何做程式「設計」,我們用開發一個 Timer 的程式來示範這個設計流程。

這個範例中,某些架構或許並非需要,只是用來演示可能的過程。且開發者具備基本的 Android 知識,並非從 0 開始。

需求確認

Timer 的需求如下:

  • Timer 時間上限為 60 分鐘,下限為 1 分鐘,預設為 1 分鐘。
  • 使用者可以按下增加/減少時間按鈕,調整 Timer 的時間,一次調整以 1 分鐘為單位。
  • 使用者可以按下開始鍵,開始倒數。
  • 使用者可以按下暫停鍵,暫停倒數。
  • 使用者可以按下停止鍵,停止倒數。
  • 使用者可以從通知列得知目前的剩餘時間。
  • 若倒數尚未結束,使用者離開 APP 之後必須要能夠繼續倒數

技術選擇

  • 背景執行:Service
  • Timer 主體:flow + delay
  • 畫面處理: Compose UI
  • 架構框架:MVI (Model-View-Intent)

針對這些技術選擇,我們來稍微解釋一下:

為什麼使用Service

從官方文件來看:

因為 Timer 需要能在使用者離開 APP 後能繼續倒數,所以我們需要一個能一直持續執行的角色,這時候Service肯定是首選。

其他的選擇,像是WorkerManager, Thread, … 等等,都可以去查一下差異,會更了解哪些時機點該使用哪些元件。

為什麼使用flow + delay

沒為什麼,因為想用。

CountDownTimerHandler + postDelayed 也是可以。

為什麼使用 Compose

因為想畫的 UI 用Compose來堆積會比較快,所以選Compose

寫了幾隻 Compose 範例之後,個人認為對比於傳統的xml來說,開發速度上真的快上滿多。

為什麼使用 MVI

因為Compose UI 現在官方主推的就是MVI,就來試試看吧。

至於 MVVM 與 MVI 的差別,本篇暫不討論

實作與重構循環

確定使用哪些技術,開始實作之後,建議先畫個設計圖

你可能第一版會畫出這樣的東西:

基本的ActivityService溝通的架構

然後你突然想到,不是說好要用MVI嗎?那個ViewModel怎麼不見了?於是

隔了一層 ViewModel 畫出了第二版:

基本的MVVM / MVI架構

一邊查資料一邊實作的過程,被你看到了MVVM + Repository 的概念,你決定修改

加上 Repo 有了第三版:

ViewModel與Repository架構

改到一半發現大家都在討論依賴反轉原則,你又動搖了。看了一下教學,覺得也沒有難改到哪邊去,決定一次到位給他改下去

加入抽象化的概念有了第四版:

將ViewModel、Repository與Service的依賴關係抽象化

心想這樣肯定沒問題了吧,加緊腳步的開發吧!

正當你快完成的時候,PM 發來一個會議通知:「Timer 需求變更討論會議」。

抱者忐忑不安的心進到會議室之後,PM 試探性的問了一句:「你的 Timer 應該可以在任何地方使用吧?我們現在很多地方要用這個 Timer!」

你開始覺得不太妙,因為你的 Timer 其實就是 Service 本人,你想了一想

把 Timer 獨立於 Service,畫出了第五版:

Timer獨立於Service之外

短短的幾天你已經改了五版了,實作重構的循環已經經歷了五次了,回到我的這篇文章的出發點上:

你怎麼知道要這樣寫?

因為出發點不一樣,有經驗的人起步可能就是無經驗者的第四個循環、第五個循環,當然做得又快又好。

所以經驗的累積,就是體現在自己下一次的起步點往後面的循環移動,至於怎麼做,就看每個人的學習記憶方法了,期許各位都能成為被問這句話的人,也能把自己的經驗分享,讓大家都一起成長!

最後附上這次範例的程式碼

範例程式中使用了startService()bindService()。常有人不確定什麼時候要使用哪一種。

這兩者的最大的差異在於有沒有需要持有 Service 的實體,如有需要就使用 bindService(),反之則使用 startService()。

生命週期的部分,用 bindService() 拉起來的 Service,只要沒有 Client 與 Service 維持 bond 的關係就會自動消滅。若是由 startService() 叫起的Service,則需要搭配 stopService(),才能將 Service 停止。

混用的生命週期則可以自己插 log 做個實驗,也能參考範例中的 Log ,TAG 為FlowTimerService

那故事到這邊就結束了嗎?

並沒有。

還記得我們提過的回饋檢討嗎?

科技進步得很快,會有新的框架不斷問世,改善現有的問題,到時候就會有第六版、第七版。

想法也改變的很快,客戶需求變更隨時來,第八、九、十版也很常見。

既然改變無可避免,我們能做的,就是寫出當每次改變來臨都可以簡單應對的 code。

能夠很快應對改變、能夠很改很少的 code 就達到目標,這就是資深工程師的精華所在,也是價值所在。

大家一起加油吧💪

--

--