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

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

前言

你是否在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

标签:android,lifecycle
0
投稿

猜你喜欢

  • 最详细的文件上传下载实例详解(推荐)

    2021-12-12 08:18:13
  • Spring Boot如何通过CORS处理跨域问题

    2021-09-10 02:44:15
  • Java 数据结构之删除链表中重复的结点

    2023-11-28 15:36:22
  • Spring Cloud Gateway替代zuul作为API网关的方法

    2023-05-03 07:19:58
  • VS2015为console.readkey添加代码片段的方法

    2023-11-15 05:23:41
  • 详解SpringBoot项目的创建与单元测试

    2021-06-17 05:13:17
  • Android开发登陆案例

    2022-03-19 10:13:01
  • idea2017建立jsp工程及tomcat配置教程

    2023-06-04 20:24:40
  • Java Scala实现数据库增删查改操作详解

    2022-02-03 05:09:22
  • SpringBoot整合Web开发之Json数据返回的实现

    2023-04-27 05:06:51
  • C#中使用闭包与意想不到的坑详解

    2022-08-15 00:13:58
  • Android使用Gridview单行横向滚动显示

    2021-07-28 17:40:43
  • .net实现序列化与反序列化实例解析

    2022-08-05 06:42:08
  • C#中WinForm程序退出方法技巧总结

    2022-01-03 12:42:21
  • C#的十种语法糖介绍

    2022-07-24 04:55:49
  • Java 确保某个Bean类被最后执行的几种实现方式

    2021-09-28 09:53:49
  • Android5.0中多种水波纹效果的实现代码

    2023-09-23 04:42:53
  • Java方法的返回值及注意事项小结

    2022-10-02 16:55:28
  • C#高性能动态获取对象属性值的步骤

    2022-08-13 13:23:12
  • java hasNextInt判断是否为数字的方法

    2023-05-17 01:51:12
  • asp之家 软件编程 m.aspxhome.com