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

目录
  • 前言
  • 场景
  • 优化版本1
  • 优化版本2
  • 单元测试
  • 总结

前言

你是否在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好维护的代码内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2022-05-04

Android框架组件Lifecycle的使用详解

1.前言 Lifecycle是Google推出的一系列的框架组件的其中一个,主要是用来感知Activity和Fragment的生命周期. 本文主要介绍如何使用Lifecycle. 2.一个常见的开发例子 public class TestActivity extends Activity{ @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceSta

Android Jetpack架构组件Lifecycle详解

前言 Lifecycle是Jetpack架构组件中用来感知生命周期的组件,使用Lifecycles可以帮助我们写出和生命周期相关更简洁更易维护的代码. 生命周期 生命周期这个简单而又重要的知识相信大家早已耳熟能详.假设我们现在有这样一个简单需求: 这个需求只是一个实例,在真实的开发中当然不可能有这样的需要: 在Activity 可见的时候,我们去做一个计数功能,每隔一秒 将计数加1 ,当Activity不可见的时候停止计数,当Activity被销毁的时候 将计数置为0 OK,So easy~ ,

浅谈Android的Lifecycle源码分析

1. 简介 很早就听说了Google的Lifecycle组件,因为项目没有使用过,所以并没有过多的接触.不过最近看到了一篇文章,其中的一条评论提到了LiveData.恰巧这两天工作内容不多,所以赶紧研究一波! 不过在看LiveData之前,我觉得还是先看下Lifecycle吧(Lifecycle更像是LiveData的基础). 2. Lifecycle的简单介绍 Lifecycle的介绍,我们还是拿Google的官方文档作为参考吧. Lifecycle主要解决的是业务和Activity/Frag

Android实例HandlerThread源码分析

HandlerThread 简介: 我们知道Thread线程是一次性消费品,当Thread线程执行完一个耗时的任务之后,线程就会被自动销毁了.如果此时我又有一 个耗时任务需要执行,我们不得不重新创建线程去执行该耗时任务.然而,这样就存在一个性能问题:多次创建和销毁线程是很耗 系统资源的.为了解这种问题,我们可以自己构建一个循环线程Looper Thread,当有耗时任务投放到该循环线程中时,线程执行耗 时任务,执行完之后循环线程处于等待状态,直到下一个新的耗时任务被投放进来.这样一来就避免了多次

Android LayoutInflater.inflate源码分析

LayoutInflater.inflate源码详解 LayoutInflater的inflate方法相信大家都不陌生,在Fragment的onCreateView中或者在BaseAdapter的getView方法中我们都会经常用这个方法来实例化出我们需要的View. 假设我们有一个需要实例化的布局文件menu_item.xml: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" an

浅谈Android Classloader动态加载分析

ClassLoader概念 我们知道,Java源文件(.java)经过编译器编译之后,会转换成Java字节码(.class),然而程序是如何加载这些字节码文件到内存中呢?这就用到了ClassLoader,即类加载器.ClassLoader类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例.从而只有class文件被载入到了内存之后,才能被其程序所引用.所以ClassLoader就是用来动态加载class文件到内存当中用的. ClassLoader的分类 An

浅谈SpringCloud之zuul源码解析

zuul各版本实现存在一些微小的变化,总的实现思想未改变,以spring-cloud-netflix-core-1.3.6.RELEASE为例 一.zuul的重要的初始化类 org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration org.springframework.cloud.netf

浅谈bootstrap源码分析之tab(选项卡)

实现tab选项卡的应用,此插件相对比较简单 源码文件: tab.js 实现原理 1.单击一个元素时,首先将原来高亮的元素取消 2.然后给被单击元素进行高亮 3.如果单击元素是下拉框中某个选项,则选中本身,还要选中下拉框 5.如果定义了动画,先执行动画,然后回调 源码分析: 1.Show方法,是在单击一个元素的时候触发,会触发如下四个事件 1.1.Hiden.bs.tab:隐藏上一个元素 1.2.Show.bs.tab:显示当前元素 1.3.Hideen.bs.tab:隐藏上一个元素完成 1.4.

浅谈bootstrap源码分析之scrollspy(滚动侦听)

源码文件: Scrollspy.js 实现功能 1.当滚动区域内设置的hashkey距离顶点到有效位置时,就关联设置其导航上的指定项 2.导航必须是 .nav > li > a 结构,并且a上href或data-target要绑定hashkey 3.菜单上必须有.nav样式 4.滚动区域的data-target与导航父级Id(一定是父级)要一致 <div id="selector" class="navbar navbar-default">

Android透明化和沉浸式状态栏实践及源码分析

本文所提到的透明状态栏其实指的是将顶部的导航栏延伸到状态栏,使之浑然一体(Google官方建议状态栏颜色比导航栏的颜色略深一点),并不代表一定不设置背景色,比如导航栏是白色,则可设置状态栏为白色,视情况而定. 相比于iOS系统,Android系统对于状态栏的设置就显得稍微复杂了一点.Android系统提供了API 19以上对状态栏的设置接口,而直到API 23以上才提供对于icon颜色的设置,还有就是各家厂商(如魅族,小米等)对于状态栏的有自己的定制,对于需要使用浅色背景状态栏的应用,没处理好的

Android AsyncTask源码分析

Android中只能在主线程中进行UI操作,如果是其它子线程,需要借助异步消息处理机制Handler.除此之外,还有个非常方便的AsyncTask类,这个类内部封装了Handler和线程池.本文先简要介绍AsyncTask的用法,然后分析具体实现. 基本用法 AsyncTask是一个抽象类,我们需要创建子类去继承它,并且重写一些方法.AsyncTask接受三个泛型参数: Params: 指定传给任务执行时的参数的类型 Progress: 指定后台任务执行时将任务进度返回给UI线程的参数类型 Re

Android getJSONObject与optJSONObject的区别结合源码分析

Android getJSONObject与optJSONObject的区别结合源码分析 json解析常见问题: getJSONObject与optJSONObject的区别,下面结合源码和案例来分析当我们使用这两周方法来解析数据时,哪种比较好. 源码分析: //使用getJSONObject时,如果返回的对象不是JSONObject,抛出JSONException异常 /** * Returns the value mapped by {@code name} if it exists and