網站首頁 編程語言 正文
前言
你是否在onStart()啟動過某項任務卻忘記在onStop()中取消呢?人不是機器,難免會有錯漏。就算老手不會犯錯,也不能保證新人不會。學會下面的小技巧,讓這種粗心成為不可能。
關于Lifecycle的源碼,已經有很多大佬分析過。這篇文章的主旨是讓讀者對Lifecycle的使用場景有更多的體會,這樣也能更好地理解源碼。先來看一個場景,然后一步一步優化。
場景
假設我們有一個界面,模擬一個廚房。里面有灶臺和餐桌。要求每秒鐘翻炒一下,總共10秒。一種常規的實現如下:
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秒,代碼會變得很長
優化版本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能做什么。
優化版本2
首先讓MyCountDownTimer實現DefaultLifecycleObserver,這樣它就是lifecycle-aware的了。這有什么用呢?有了這個,MyCountDownTimer就能在fragment/activity生命周期發生變化的時候得到通知并在內部處理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之間的耦合度,讓代碼更好維護。
原文鏈接:https://juejin.cn/post/7088171300162076680
相關推薦
- 2022-07-02 webpack 配置file-loader統一字體打包文件輸出目錄后dist下仍然有字體打包文件
- 2022-07-09 ABP基礎架構深入探索_基礎應用
- 2022-12-11 python中windows鏈接linux執行命令并獲取執行狀態的問題小結_python
- 2022-06-10 Flutter實現心動的動畫特效_Android
- 2022-10-22 Python?修改CSV文件實例詳解_python
- 2022-10-20 react實現動態表單_React
- 2022-05-15 Redis中有序集合的內部實現方式的詳細介紹_Redis
- 2022-04-25 C++的類型轉換(強轉)你了解嗎_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支