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
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
最详细的文件上传下载实例详解(推荐)
![](https://img.aspxhome.com/file/2023/0/77940_0s.png)
Spring Boot如何通过CORS处理跨域问题
Java 数据结构之删除链表中重复的结点
![](https://img.aspxhome.com/file/2023/6/60346_0s.gif)
Spring Cloud Gateway替代zuul作为API网关的方法
![](https://img.aspxhome.com/file/2023/8/69738_0s.jpg)
VS2015为console.readkey添加代码片段的方法
![](https://img.aspxhome.com/file/2023/3/108693_0s.png)
详解SpringBoot项目的创建与单元测试
![](https://img.aspxhome.com/file/2023/2/65152_0s.png)
Android开发登陆案例
![](https://img.aspxhome.com/file/2023/3/137743_0s.png)
idea2017建立jsp工程及tomcat配置教程
![](https://img.aspxhome.com/file/2023/2/119052_0s.png)
Java Scala实现数据库增删查改操作详解
![](https://img.aspxhome.com/file/2023/0/61830_0s.png)
SpringBoot整合Web开发之Json数据返回的实现
![](https://img.aspxhome.com/file/2023/9/61639_0s.png)
C#中使用闭包与意想不到的坑详解
![](https://img.aspxhome.com/file/2023/8/116618_0s.png)
Android使用Gridview单行横向滚动显示
.net实现序列化与反序列化实例解析
C#中WinForm程序退出方法技巧总结
C#的十种语法糖介绍
![](https://img.aspxhome.com/file/2023/3/106493_0s.jpg)
Java 确保某个Bean类被最后执行的几种实现方式
![](https://img.aspxhome.com/file/2023/8/99758_0s.png)
Android5.0中多种水波纹效果的实现代码
![](https://img.aspxhome.com/file/2023/9/91719_0s.gif)
Java方法的返回值及注意事项小结
C#高性能动态获取对象属性值的步骤
![](https://img.aspxhome.com/file/2023/7/104297_0s.png)