Android實(shí)用小技巧之利用Lifecycle寫出更好維護(hù)的代碼
前言
你是否在onStart()啟動(dòng)過(guò)某項(xiàng)任務(wù)卻忘記在onStop()中取消呢?人不是機(jī)器,難免會(huì)有錯(cuò)漏。就算老手不會(huì)犯錯(cuò),也不能保證新人不會(huì)。學(xué)會(huì)下面的小技巧,讓這種粗心成為不可能。
關(guān)于Lifecycle的源碼,已經(jīng)有很多大佬分析過(guò)。這篇文章的主旨是讓讀者對(duì)Lifecycle的使用場(chǎng)景有更多的體會(huì),這樣也能更好地理解源碼。先來(lái)看一個(gè)場(chǎng)景,然后一步一步優(yōu)化。
場(chǎng)景
假設(shè)我們有一個(gè)界面,模擬一個(gè)廚房。里面有灶臺(tái)和餐桌。要求每秒鐘翻炒一下,總共10秒。一種常規(guī)的實(shí)現(xiàn)如下:
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
}
}潛在問(wèn)題:
- 在別的地方實(shí)現(xiàn)類似的功能需要把很多重復(fù)代碼復(fù)制過(guò)去
- 忘記cancel()可能會(huì)造成一系列的麻煩
- 當(dāng)產(chǎn)品經(jīng)理突然提出要同時(shí)顛勺5秒以及擦桌子20秒,代碼會(huì)變得很長(zhǎng)
優(yōu)化版本1
先解決第一個(gè)問(wèn)題,把CountDownTimer放到一個(gè)單獨(dú)的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
}
}需要復(fù)用時(shí),只需傳入需要改動(dòng)的參數(shù)/方法:
// NeighbourKitchenFragment.kt
class NeighbourKitchenFragment : Fragment() {
private val timer: CountDownTimer? = null
override fun onResume() {
...
timer = MyCountDownTimer(onTickAction = { 翻炒 },onFinishAction = { 甩鍋 })
timer.start()
}
override fun onPause() {
timer?.cancel()
...
}
}復(fù)用起來(lái)好像方便了一點(diǎn),但是當(dāng)上面提到過(guò)的的問(wèn)題3出現(xiàn)時(shí),代碼會(huì)變成:
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
}
}隨著需求增加,F(xiàn)ragment變得越來(lái)越長(zhǎng),也更難維護(hù)。同時(shí),當(dāng)在onResume中添加timer時(shí)被同事打斷,之后就有可能會(huì)忘記在onPause中cancel()。有沒(méi)有辦法解決這些問(wèn)題呢?
接下來(lái)切入正題,讓我們看看Lifecycle能做什么。
優(yōu)化版本2
首先讓MyCountDownTimer實(shí)現(xiàn)DefaultLifecycleObserver,這樣它就是lifecycle-aware的了。這有什么用呢?有了這個(gè),MyCountDownTimer就能在fragment/activity生命周期發(fā)生變化的時(shí)候得到通知并在內(nèi)部處理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時(shí)自動(dòng)開始
override fun onResume(owner: LifecycleOwner) {
start()
}
// onPause時(shí)自動(dòng)取消
override fun onPause(owner: LifecycleOwner) {
cancel()
}
// onDestroy時(shí)停止觀察
override fun onDestroy(owner: LifecycleOwner) {
owner.lifecycle.removeObserver(this)
}
compaion object {
private const val DEFAULT_DURATION_IN_MILLIS = 10000L
}
}上面例子中的KitchenFragment將會(huì)變成這樣:
class KitchenFragment : Fragment() {
override fun onCreate() {
...
initTimer()
}
private fun initTimer() {
// 翻炒任務(wù)
val timer1 = MyCountDownTimer(onTickAction = { 翻炒 },onFinishAction = { 出鍋 })
// 顛勺任務(wù)
val timer2 = MyCountDownTimer(millisUntilFinished = BRITAIN_SPOON_DURATION_IN_MILLIS,onTickAction = { 顛勺 })
// 擦桌任務(wù)
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中只需要專注于添加需要的功能,不用操心取消任務(wù)與停止觀察。既清爽又不容易犯錯(cuò)。
單元測(cè)試
因?yàn)檫壿嫶a都封裝在MyCountDownTimer,主要測(cè)試這個(gè)class就可以了。不需要給每一個(gè)使用MyCountDownTimer的Fragment都寫詳細(xì)的測(cè)試。
只需要mock一個(gè)LifecycleOwner就足夠,也不需要啟動(dòng)一個(gè)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)
// 檢測(cè)是否開始翻炒,出鍋
...
}
}總結(jié)
通過(guò)把重復(fù)的代碼和邏輯封裝在自定義的LifecycleObserver內(nèi)部,不僅可以給Activity/Fragment“瘦身”,防止忘記在onStop()/onDestroy()中收拾,還可以使復(fù)用代碼更加方便。同時(shí)也遵循設(shè)計(jì)模式,降低了Fragment與Timer之間的耦合度,讓代碼更好維護(hù)。
到此這篇關(guān)于Android實(shí)用小技巧之利用Lifecycle寫出更好維護(hù)的代碼的文章就介紹到這了,更多相關(guān)Android Lifecycle好維護(hù)的代碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android仿微博首頁(yè)Tab加號(hào)彈窗功能
這篇文章主要為大家詳細(xì)介紹了Android仿微博首頁(yè)Tab加號(hào)彈窗功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
Convert WebP to PNG using java
本文主要介紹Convert WebP to PNG using java,這里對(duì) WebP 做了詳細(xì)說(shuō)明,并講解Linux 環(huán)境下WebP 轉(zhuǎn)png格式的示例,有興趣的小伙伴可以參考下2016-08-08
Android不顯示開機(jī)向?qū)Ш烷_機(jī)氣泡問(wèn)題
這篇文章主要介紹了Android不顯示開機(jī)向?qū)Ш烷_機(jī)氣泡問(wèn)題,需要的朋友可以參考下2019-05-05
Android如何自定義升級(jí)對(duì)話框示例詳解
對(duì)話框是我們?cè)谄綍r(shí)經(jīng)常會(huì)遇到的一個(gè)功能,但自帶的對(duì)話框不夠美觀,大家一般都會(huì)自定義,下面這篇文章主要給大家介紹了關(guān)于Android如何自定義升級(jí)對(duì)話框的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-08-08
Android實(shí)現(xiàn)定時(shí)器的3種方法
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)定時(shí)器的3種方法,感興趣的小伙伴們可以參考一下2016-07-07
Android利用BitMap獲得圖片像素?cái)?shù)據(jù)的方法
這篇文章主要介紹了Android利用BitMap獲得圖片像素?cái)?shù)據(jù)的方法,結(jié)合實(shí)例對(duì)比分析了Android獲取圖片像素?cái)?shù)據(jù)的相關(guān)技巧,需要的朋友可以參考下2016-02-02
flutter實(shí)現(xiàn)一個(gè)列表下拉抽屜的示例代碼
本文主要介紹了flutter實(shí)現(xiàn)一個(gè)列表下拉抽屜的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
詳解AndroidStudio中代碼重構(gòu)菜單Refactor功能
這篇文章主要介紹了AndroidStudio中代碼重構(gòu)菜單Refactor功能詳解,本文通過(guò)代碼演示,功能截圖來(lái)詳細(xì)說(shuō)明as為大名重構(gòu)提供的各項(xiàng)功能,需要的朋友可以參考下2019-11-11
Android ViewPager實(shí)現(xiàn)無(wú)限循環(huán)的實(shí)例
這篇文章主要介紹了Android ViewPager實(shí)現(xiàn)無(wú)限循環(huán)的實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-07-07

