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

 更新时间:2022年05月05日 17:09:00   作者:厨师小p  
lifecycle是一个类,用于存储有关组件(如Activity或Fragment)的生命周期状态的信息,并允许其他对象观察此状态,下面这篇文章主要给大家介绍了关于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实用小技巧之利用Lifecycle写出更好维护的代码的文章就介绍到这了,更多相关Android Lifecycle好维护的代码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Android仿微博首页Tab加号弹窗功能

    Android仿微博首页Tab加号弹窗功能

    这篇文章主要为大家详细介绍了Android仿微博首页Tab加号弹窗功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-05-05
  • android 放大镜ShapeDrawable妙用分享

    android 放大镜ShapeDrawable妙用分享

    android上想实现局部放大的效果,比如画面中加个放大镜的效果,发现ShapeDrawable是一个最好的选择。
    2013-06-06
  • Convert WebP to PNG using java

    Convert WebP to PNG using java

    本文主要介绍Convert WebP to PNG using java,这里对 WebP 做了详细说明,并讲解Linux 环境下WebP 转png格式的示例,有兴趣的小伙伴可以参考下
    2016-08-08
  • Android不显示开机向导和开机气泡问题

    Android不显示开机向导和开机气泡问题

    这篇文章主要介绍了Android不显示开机向导和开机气泡问题,需要的朋友可以参考下
    2019-05-05
  • Android如何自定义升级对话框示例详解

    Android如何自定义升级对话框示例详解

    对话框是我们在平时经常会遇到的一个功能,但自带的对话框不够美观,大家一般都会自定义,下面这篇文章主要给大家介绍了关于Android如何自定义升级对话框的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-08-08
  • Android实现定时器的3种方法

    Android实现定时器的3种方法

    这篇文章主要为大家详细介绍了Android实现定时器的3种方法,感兴趣的小伙伴们可以参考一下
    2016-07-07
  • Android利用BitMap获得图片像素数据的方法

    Android利用BitMap获得图片像素数据的方法

    这篇文章主要介绍了Android利用BitMap获得图片像素数据的方法,结合实例对比分析了Android获取图片像素数据的相关技巧,需要的朋友可以参考下
    2016-02-02
  • flutter实现一个列表下拉抽屉的示例代码

    flutter实现一个列表下拉抽屉的示例代码

    本文主要介绍了flutter实现一个列表下拉抽屉的示例代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • 详解AndroidStudio中代码重构菜单Refactor功能

    详解AndroidStudio中代码重构菜单Refactor功能

    这篇文章主要介绍了AndroidStudio中代码重构菜单Refactor功能详解,本文通过代码演示,功能截图来详细说明as为大名重构提供的各项功能,需要的朋友可以参考下
    2019-11-11
  • Android ViewPager实现无限循环的实例

    Android ViewPager实现无限循环的实例

    这篇文章主要介绍了Android ViewPager实现无限循环的实例的相关资料,需要的朋友可以参考下
    2017-07-07

最新评论