Android:封装一个真正的倒计时工具

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 16 天,点击查看活动详情

倒计时功能相信大家都遇到过,特别是在获取验证码时,为了防止用户频繁获取会设置一个倒计时,通常是60s,超过时限后又可继续点击获取按钮。还有就是电商应用中某些商品活动、下单未支付的待支付倒计时等。

其实实现的方式相当多,例如Handler配合sendEmptyMessageDelayed()或者Runnable,Time配合TimeTask。又或者早期的RxJava系列的interval操作符,Kotlin中的Flow(这里涉及协程)等等。但如果不稍加处理,其中都会存在一个问题:当倒计时正在进行的时候退出该页面,之后再次进入页面以前的时间又重置了,不能达到真正的倒计时功能。那么,如何实现真正的倒计时功能呢?本文就使用原生工具CountDownTimer实现一个真正的倒计时工具(其他方式也可效仿实现)。

首先回顾一下CountDownTimer的使用方法:

public abstract class CountDownTimer {
    public CountDownTimer(long millisInFuture, long countDownInterval) {
        throw new RuntimeException("Stub!");
    }

    public final synchronized void cancel() {
        throw new RuntimeException("Stub!");
    }

    public final synchronized CountDownTimer start() {
        throw new RuntimeException("Stub!");
    }

    public abstract void onTick(long var1);

    public abstract void onFinish();
}

构造方法内就只有倒计时总时长和间隙时长两个属性,其次onTick方法为剩余时长,其他的就是一些开始结束和取消方法。

上面说到我们需要实现真正的倒计时功能,那么要满足需求我们就要把当前的时间戳和倒计时时长做一个绑定。如此一来,不管我在哪个页面,我只需记录倒计时开始的时间,问题就迎刃而解。这里我们把目标时间保存在本地:

private val mCountDownTimer: CountDownTimer
    get() {
        return object : CountDownTimer(getCountTimeMills(),1000){
            override fun onTick(millisUntilFinished: Long) {
                mListener?.onTick(millisUntilFinished/1000)
            }
            override fun onFinish() {
                mListener?.onFinish()
            }
        }
    }

//开始倒计时,入参总时长  保存目标时间戳为结束时间
fun start(totalTimeMills: Long){
    mTotalTimeMills = totalTimeMills+System.currentTimeMillis()
    MMKV.defaultMMKV().encode(KEY_TIME, mTotalTimeMills)
    mCountDownTimer.start()
}

//提供给CountDownTimer入参总时长
fun getCountTimeMills(): Long{
       mTotalTimeMills = MMKV.defaultMMKV().decodeLong(KEY_TIME)
       return mTotalTimeMills - System.currentTimeMillis()
}

另外,还需添加方法判断当前时间是否已经超过倒计时结束时间,仅当返回为true时才能继续调用mCountDownTimer.start():

fun isCounting(): Boolean{
    mTotalTimeMills = MMKV.defaultMMKV().decodeLong(KEY_TIME)
    return System.currentTimeMillis() < mTotalTimeMills
}

到此再添加一个接口,用于回调CountDownTimer的onTick和onFinish方法,给外部提供更新视图的入口。因为CountDownTimer内部也是使用的Handler,这里已经切换为UI线程,所以可以直接更新UI。另外在Activity中使用CountDownTimer时,需要在页面销毁的时候调用countDownTimer.cancel(),防止内存泄漏。

总结

精髓其实就是当前时间和倒计时时长做绑定,本地记录下倒计时完成的时间戳。这样就能在任何地方知道倒计时是否完成,并计算出剩余的倒计时显示到页面。特别需要注意的是要熟悉说选择倒计时的基础原理,才能在此基础上更好的掌握封装,例如上文所使用的CountDownTimer,底层也是使用的Handler,需要在离开页面时进行释放,否则很容易导致内存泄漏。

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 16 天,点击查看活动详情