一.背景
项目中需要一个获取验证码的倒计时,需要应用被杀死,再次进入能继续倒计时。如果使用系统提供的倒计时CountDownTimer,在杀死进程后,就必须重新创建对象,倒计时被清空。因此,需要自己实现倒计时,并且不能使用对象来保存时间数据,必须是持久化的,例如使用SharedPreferences来保存。
二.方案思路
方案一:使用系统时间
保存倒计时完成的时间stopTime,每过一秒,计算一次完成时间与当前时间之差,即使倒计时剩余时间leftTime。
即:
leftTime = stopTime - System.currentTimeMillis();
如此,就算杀死应用,再次进来后,存储的stopTime是不变的,倒计时依然可以继续。
缺点:
如果在倒计时过程中修改系统时间,则会导致倒计时不准确。例如,把系统时间改到一天前,则会倒计时一天才会结束。如果把时间改到几分钟后,则倒计时直接结束了。前一种情况,可以判断时间往前修改则进行特殊处理,但若时间往后改一点,则不好判断是否是认为修改了系统时间。
方案二:使用开机时间
Android 通过SystemClock.elapsedRealtime()方法可以获取手机开机的时间。使用SystemClock.elapsedRealtime()代替System.currentTimeMillis(),则不受修改系统时间的影响。无论系统时间怎么修改,倒计时都是准确的。
缺点:
如果手机关机/重启,SystemClock.elapsedRealtime()时间会重置。如果两次进入倒计时页面的时间相近,则可能会出现异常。例如,在开机一个小时候开始倒计时60秒,倒计时结束前,重启了手机,刚好一个小时后再次进去倒计时页面,则会发现倒计时在继续。
方案三:使用开机时间+服务器时间
既然关机、重启会重置SystemClock.elapsedRealtime()时间,那可不可以监听手机的开关机广播,来进行特殊处理呢?当然可以!方法可参考:https://blog.csdn.net/github_27263697/article/details/77848177
然而,则不是最优方案,某些情况下应用可能无法监听到开关机广播,详情可参考:https://blog.csdn.net/baidu_27196493/article/details/78269674
监听开关机广播不靠谱,那有没有其他办法知道是否可以开关机?
看了这篇文章,找到了判断方法。https://blog.csdn.net/weixin_34267123/article/details/87411492
可以用服务器的时间serverTime与开机时间的elapsedRealtime相减,得到一个时间差desTime。如果两次进入倒计时的时间差相差很大,则说明中间经历过关机/重启,需要对倒计时进行特殊处理(通常为重设倒计时)。
desTime = serverTime - elapsedRealtime;
注:
1.自己测试的desTime相差大概有1秒左右,当然根据网速,这个差距可能会拉大。但是一般不会超过接口的网络超时时间。
2.手机关机/重启后,可以直接认为倒计时已结束。如果这种情况下还想继续倒计时,要么使用方案一、要么可以计算两次desTime之差,从而估算出关机到重新开机用了多少时间,抠除这段时间后继续计时,但是这种方案估算的时间未必准确。
方案四:使用系统时间+服务器时间
SystemClock.elapsedRealtime()会受关机/重启的影响,而System.currentTimeMillis()不会,如果想不受开关机影响,可以使用System.currentTimeMillis() + 服务器时间,其实就是https://blog.csdn.net/weixin_34267123/article/details/87411492中介绍的方法。说到这里,又绕回去了:System.currentTimeMillis()的缺点十分明显,用户可以随意修改系统时间,修改系统时间的成本低开关机更小,有没有办法解决这个问题呢?可以在用户修改系统时间后,杀死应用,启动应用的时候,再获取服务器时间戳,进行一次校准。
假如第一次启动时,服务器时间是serverTime0,本地时间是systemTime0。开始倒计时的本地时间为systemTime1,倒计时T秒。则,应该在systemTime1+T时刻结束(如果systemTime保持不变),或者在sytemTime1 + (serverTime0 - systemTime0) + T时刻结束。显然,第二种及时方法比第一种更加稳定。过了t1秒后,应该剩余T-t1秒,计时显示剩余systemTime1 + (serverTime0 - systemTime0) -[systemTime1 + t1 + (serverTIme0 - seystemTime0)] = T - t1秒,计时准确。
假如过了t2秒,中途修改过一次系统时间,前进了D秒。则重新校准时服务器时间serverTime2,本地时间为systemTime2,两次校准的网络传输时延误差为u。
则serverTime2 = serverTime0 + t2;systemTime2 = systemTime0 + t2 + D + u;
应该剩余T-t2秒,而计时显示:
systemTime1 + (serverTime0 - systemTime0) + T - [systemTime1 + D + t2 + (serverTime2 - systemTime2)] = (serverTime0 - systemTime0) - (servierTime2 - systemTime2) - D + T - t2 = T - t 2+ u;
采用此方案,因存在网络传输时延,不能保证计时准确。但有一个优点,只要有网络,就能校准时间,不管是否关机/重启,或者修改系统时间,倒计时都能近似准确,可用于对倒计时精确度要求不高的场景,且不依赖Android系统,是可跨平台的方案。
三.代码实现
平时看博客最烦别人不留下代码给复制粘贴。所以我就不留下代码了。皮皮虾,我们走~