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) // 检测是否开始翻炒,出锅 ... } }
总结
通过把重复的代码和逻辑封装在自定义的LifecycleObserver内部,不仅可以给Activity/Fragment“瘦身”,防止忘记在onStop()/onDestroy()中收拾,还可以使复用代码更加方便。同时也遵循设计模式,降低了Fragment与Timer之间的耦合度,让代码更好维护。
栏 目:Android代码
本文标题:Android实用小技巧之利用Lifecycle写出更好维护的代码
本文地址:http://www.codeinn.net/misctech/204417.html