软件编程
位置:首页>> 软件编程>> Android编程>> Android实用小技巧之利用Lifecycle写出更好维护的代码

Android实用小技巧之利用Lifecycle写出更好维护的代码

作者:厨师小p  发布时间:2021-09-07 14:00:09 

标签:android,lifecycle

前言

你是否在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)
       // 检测是否开始翻炒,出锅
       ...
   }
}

来源:https://juejin.cn/post/7088171300162076680

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com