Android實用小技巧之利用Lifecycle寫出更好維護的代碼
前言
你是否在onStart()啟動過某項任務卻忘記在onStop()中取消呢?人不是機器,難免會有錯漏。就算老手不會犯錯,也不能保證新人不會。學會下面的小技巧,讓這種粗心成為不可能。
關于Lifecycle的源碼,已經有很多大佬分析過。這篇文章的主旨是讓讀者對Lifecycle的使用場景有更多的體會,這樣也能更好地理解源碼。先來看一個場景,然后一步一步優(yōu)化。
場景
假設我們有一個界面,模擬一個廚房。里面有灶臺和餐桌。要求每秒鐘翻炒一下,總共10秒。一種常規(guī)的實現如下:
class KitchenFragment : Fragment() { private var timer: CountDownTimer? = null override fun onResume() { ... timer = object : CountDownTimer(COOKING_TIME_IN_MILLIS, SECOND_IN_MILLIS) { override fun onTick(millisUntilFinished: Long) { // 翻炒 } override fun onFinish() { // 出鍋 } } timer.start() } override fun onPause() { timer?.cancel() ... } compaion object { private const val COOKING_TIME_IN_MILLIS = 10000L } }
潛在問題:
- 在別的地方實現類似的功能需要把很多重復代碼復制過去
- 忘記cancel()可能會造成一系列的麻煩
- 當產品經理突然提出要同時顛勺5秒以及擦桌子20秒,代碼會變得很長
優(yōu)化版本1
先解決第一個問題,把CountDownTimer放到一個單獨的class。
class KitchenFragment : Fragment() { private val timer: CountDownTimer? = null override fun onResume() { ... timer = MyCountDownTimer(onTickAction = { 翻炒 },onFinishAction = { 出鍋 }) timer.start() } override fun onPause() { timer?.cancel() ... } } // MyCountDownTimer.kt class MyCountDownTimer@JvmOverloads constuctor( millisUntilFinished: Long = DEFAULT_DURATION_IN_MILLIS, countDownInterval: LONG = SECOND_IN_MILLIS, private val onTickAction: () -> Unit, private val onFinishAction: () -> Unit = {} ) : CountDownTimer(millisUntilFinished, countDownInterval) { override fun onTick(millisUntilFinished: Long) { onTickAction.invoke() } override fun onFinish() { onFinishAction.invoke() } compaion object { private const val DEFAULT_DURATION_IN_MILLIS = 10000L } }
需要復用時,只需傳入需要改動的參數/方法:
// NeighbourKitchenFragment.kt class NeighbourKitchenFragment : Fragment() { private val timer: CountDownTimer? = null override fun onResume() { ... timer = MyCountDownTimer(onTickAction = { 翻炒 },onFinishAction = { 甩鍋 }) timer.start() } override fun onPause() { timer?.cancel() ... } }
復用起來好像方便了一點,但是當上面提到過的的問題3出現時,代碼會變成:
class KitchenFragment : Fragment() { private val cookTimer1: CountDownTimer? = null private val cookTimer2: CountDownTimer? = null private val sweepTableTimer: CountDownTimer? = null override fun onResume() { ... cookTimer1 = MyCountDownTimer(onTickAction = { 翻炒 },onFinishAction = { 出鍋 }) cookTimer1.start() cookTimer2 = MyCountDownTimer(millisUntilFinished = BRITAIN_SPOON_DURATION_IN_MILLIS,onTickAction = { 顛勺 }) cookTimer2.start() sweepTableTimer = MyCountDownTimer(millisUntilFinished = SWEEP_TABLE_DURATION_IN_MILLIS,onTickAction = { 擦桌子 }) sweepTableTimer.start() } override fun onPause() { cookTimer1?.cancel() cookTimer2?.cancel() sweepTableTimer?.cancel() ... } compaion object { private const val BRITAIN_SPOON_DURATION_IN_MILLIS = 5000L private const val SWEEP_TABLE_DURATION_IN_MILLIS = 20000L } }
隨著需求增加,Fragment變得越來越長,也更難維護。同時,當在onResume中添加timer時被同事打斷,之后就有可能會忘記在onPause中cancel()。有沒有辦法解決這些問題呢?
接下來切入正題,讓我們看看Lifecycle能做什么。
優(yōu)化版本2
首先讓MyCountDownTimer實現DefaultLifecycleObserver,這樣它就是lifecycle-aware的了。這有什么用呢?有了這個,MyCountDownTimer就能在fragment/activity生命周期發(fā)生變化的時候得到通知并在內部處理cancel()等操作。
// MyCountDownTimer.kt // Lifecycle-aware CountDownTimer class MyCountDownTimer@JvmOverloads constuctor( millisUntilFinished: Long = DEFAULT_DURATION_IN_MILLIS, countDownInterval: LONG = SECOND_IN_MILLIS, private val onTickAction: () -> Unit, private val onFinishAction: () -> Unit = {} ) : CountDownTimer(millisUntilFinished, countDownInterval), DefaultLifecycleObserver { override fun onTick(millisUntilFinished: Long) { onTickAction.invoke() } override fun onFinish() { onFinishAction.invoke() } // onResume時自動開始 override fun onResume(owner: LifecycleOwner) { start() } // onPause時自動取消 override fun onPause(owner: LifecycleOwner) { cancel() } // onDestroy時停止觀察 override fun onDestroy(owner: LifecycleOwner) { owner.lifecycle.removeObserver(this) } compaion object { private const val DEFAULT_DURATION_IN_MILLIS = 10000L } }
上面例子中的KitchenFragment將會變成這樣:
class KitchenFragment : Fragment() { override fun onCreate() { ... initTimer() } private fun initTimer() { // 翻炒任務 val timer1 = MyCountDownTimer(onTickAction = { 翻炒 },onFinishAction = { 出鍋 }) // 顛勺任務 val timer2 = MyCountDownTimer(millisUntilFinished = BRITAIN_SPOON_DURATION_IN_MILLIS,onTickAction = { 顛勺 }) // 擦桌任務 val timer3 = MyCountDownTimer(millisUntilFinished = SWEEP_TABLE_DURATION_IN_MILLIS,onTickAction = { 擦桌子 }) viewLifecycleOwner.lifecycle.apply { addObserver(timer1) addObserver(timer2) addObserver(timer3) } } compaion object { private const val BRITAIN_SPOON_DURATION_IN_MILLIS = 5000L private const val SWEEP_TABLE_DURATION_IN_MILLIS = 20000L } }
在Fragment中只需要專注于添加需要的功能,不用操心取消任務與停止觀察。既清爽又不容易犯錯。
單元測試
因為邏輯代碼都封裝在MyCountDownTimer,主要測試這個class就可以了。不需要給每一個使用MyCountDownTimer的Fragment都寫詳細的測試。
只需要mock一個LifecycleOwner就足夠,也不需要啟動一個mock Fragment。
class MyCountDownTimerTest { private lateinit var timer: MyCountDownTimer private lateinit var lifeCycle: LifecycleRegistry @Before fun setUp() { val lifeCycleOwner: LifecycleOwner = mock(LifecycleOwner::class.java) lifeCycle = LifecycleRegistry(lifeCycleOwner) timer = MyCountDownTimer(onTickAction = { 翻炒 },onFinishAction = { 出鍋 }) lifeCycle.addObserver(timer) lifeCycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) } @Test fun timerActionExecuted() { lifeCycle.markState(Lifecycle.State.RESUMED) // 檢測是否開始翻炒,出鍋 ... } }
總結
通過把重復的代碼和邏輯封裝在自定義的LifecycleObserver內部,不僅可以給Activity/Fragment“瘦身”,防止忘記在onStop()/onDestroy()中收拾,還可以使復用代碼更加方便。同時也遵循設計模式,降低了Fragment與Timer之間的耦合度,讓代碼更好維護。
到此這篇關于Android實用小技巧之利用Lifecycle寫出更好維護的代碼的文章就介紹到這了,更多相關Android Lifecycle好維護的代碼內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Convert WebP to PNG using java
本文主要介紹Convert WebP to PNG using java,這里對 WebP 做了詳細說明,并講解Linux 環(huán)境下WebP 轉png格式的示例,有興趣的小伙伴可以參考下2016-08-08詳解AndroidStudio中代碼重構菜單Refactor功能
這篇文章主要介紹了AndroidStudio中代碼重構菜單Refactor功能詳解,本文通過代碼演示,功能截圖來詳細說明as為大名重構提供的各項功能,需要的朋友可以參考下2019-11-11Android ViewPager實現無限循環(huán)的實例
這篇文章主要介紹了Android ViewPager實現無限循環(huán)的實例的相關資料,需要的朋友可以參考下2017-07-07