Android Timer实现倒计时偶现负数显示异常问题求助
问题根因
出现负数倒计时是几个逻辑缺陷叠加导致的:
- 计时器泄漏:调用
startTimer()时没有先取消已有的旧Timer实例,只要重复触发启动逻辑,旧计时器就会在后台持续运行、多个实例同时修改计数,且mTimer仅保存最新实例,后续调用停止方法无法终止已泄漏的旧任务,计数会被持续扣减到负数,运行时间越长负数值越大,最终出现-03:-15这类跨分钟的负数显示。 - 执行顺序错误:原有回调逻辑先递减计数、再更新UI、最后才判断是否归0停止,一旦Timer任务出现调度排队、cancel指令延迟生效的情况,计数归0后仍会继续被扣减。
- 计数逻辑不可靠:靠每次回调给变量减1的方式计数,本身就会受Timer调度精度影响产生时间漂移,加上负数做除法、取模时不会自动转正,只要计数小于0就会显示负数格式。
- 初始时间计算存在误差:初始剩余值在
startTimer()调用瞬间计算,但Timer第一次回调默认有1000ms延迟,初始阶段就会出现计数和真实时间不匹配的问题。
修复方案
核心改动是抛弃手动递减计数的逻辑,改用目标时间戳差值计算剩余时间,从根源避免计数漂移、多扣问题,同时补上任务泄漏兜底、负数拦截逻辑,修复后代码如下:
import java.text.SimpleDateFormat import java.util.* import kotlin.concurrent.timer private var mTimer: Timer? = null // 记录本轮倒计时的截止时间戳 private var targetEndMillis = 0L fun getNowTimeSecond() : String { val now: Long = System.currentTimeMillis() val date = Date(now) // 补全Locale参数,避免特殊地区格式异常 val dateFormat = SimpleDateFormat("ss", Locale.getDefault()) return dateFormat.format(date) } private fun startTimer() { // 启动新计时器前先终止旧任务,彻底避免泄漏 stopTimer() // 计算本轮倒计时的截止时间点 val initRemainSecond = 60 - getNowTimeSecond().toInt() targetEndMillis = System.currentTimeMillis() + initRemainSecond * 1000L mTimer = timer(period = 1000) { // 每次回调基于真实时间差算剩余值,不受调度精度影响 val remainSecond = ((targetEndMillis - System.currentTimeMillis()) / 1000).toInt() // 剩余时间归0时更新显示后停止 if (remainSecond <= 0) { _time.postValue("Time : 00:00") stopTimer() return@timer } // 此时remainSecond必然大于0,格式化不会出现负数 val minuteStr = (remainSecond / 60).toString().padStart(2, '0') val secondStr = (remainSecond % 60).toString().padStart(2, '0') _time.postValue("Time : $minuteStr:$secondStr") } } private fun stopTimer() { mTimer?.cancel() mTimer = null }
如果是在Android平台使用,更推荐用
CountDownTimer或者协程delay实现倒计时,比原生Timer对生命周期的适配性更好。
内容的提问来源于stack exchange,提问作者Lucy
相关产品推荐
相关产品推荐

