You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为CountDownTimer的resume操作添加非阻塞2秒延迟

问题原因

之前直接用sendMessageDelayed添加2秒延迟出现主线程卡死、逻辑异常,核心是两个逻辑漏洞:

  1. 调用resume时立刻计算了mStopTimeInFuture(基于当前系统启动时间加剩余倒计时时长),但实际倒计时2秒后才启动,等Handler收到消息执行时,系统时间已经多走了2秒,会导致timeLeft()计算出的剩余时间直接为负,打乱原有倒计时的消息调度逻辑。
  2. 延迟等待的2秒内直接把mPauseTimeRemaining设为0,导致计时器状态判定错误(此时既不属于暂停也不属于运行),cancel方法也没有清理延迟启动的消息,容易出现消息重复发送、锁等待异常。
修复方案

不需要改动原有倒计时核心调度逻辑,只需要单独处理resume前的2秒延迟状态,复用已经定义但未使用的MSG2作为延迟启动的消息标记即可,全程基于主线程Handler调度,不会阻塞主线程,也不会破坏原有倒计时精度:

  1. 修改cancel方法,同时移除倒计时消息和延迟启动消息
  2. 修改resume逻辑,延迟2秒发送启动消息,等待期间保持暂停状态,不提前计算停止时间
  3. 在Handler的消息处理中新增MSG2的分支,收到启动消息后再执行真正的倒计时启动逻辑
改动后的核心代码
/**
 * Cancel the countdown and clears all remaining messages
 */
fun cancel() {
    // 同时移除两类消息,避免残留消息触发异常
    mHandler.removeMessages(MSG)
    mHandler.removeMessages(MSG2)
}

/**
 * Resumes the counter.
 */
fun resume() {
    if (isPaused) {
        // 先清空所有残留消息,避免重复触发
        cancel()
        // 延迟2秒发送启动消息,不阻塞主线程
        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG2), 2000)
    }
}

// handles counting down
private val mHandler: Handler = object : Handler(Looper.getMainLooper()) {
    override fun handleMessage(msg: Message) {
        synchronized(this@CountDownTimer) {
            when(msg.what) {
                MSG2 -> {
                    // 2秒延迟到了,真正启动倒计时
                    mMillisInFuture = mPauseTimeRemaining
                    // 此时再计算停止时间,保证时间精度
                    mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture
                    sendMessage(obtainMessage(MSG))
                    mPauseTimeRemaining = 0
                }
                MSG -> {
                    // 原有倒计时逻辑完全不动,保证原有运行逻辑不受影响
                    val millisLeft = timeLeft()
                    if (millisLeft <= 0) {
                        cancel()
                        onFinish()
                    } else if (millisLeft < mCountdownInterval) {
                        // no tick, just delay until done
                        sendMessageDelayed(obtainMessage(MSG), millisLeft)
                    } else {
                        val lastTickStart = SystemClock.elapsedRealtime()
                        onTick(millisLeft)
                        // take into account user's onTick taking time to execute
                        var delay =
                            mCountdownInterval - (SystemClock.elapsedRealtime() - lastTickStart)

                        // special case: user's onTick took more than mCountdownInterval to
                        // complete, skip to next interval
                        while (delay < 0) delay += mCountdownInterval
                        sendMessageDelayed(obtainMessage(MSG), delay)
                    }
                }
            }
        }
    }
}
补充说明
  • 2秒等待期间调用pause、cancel都会正常移除延迟启动消息,不会出现倒计时偷跑的问题
  • 2秒等待时间不会计入倒计时时长,恢复后剩余时间和暂停时完全一致
  • 原有onTick、onFinish的触发逻辑、时间精度完全没有改动,不会破坏原有业务逻辑
  • 如果不需要首次创建启动时的2秒延迟,可以给resume加个可选参数skipDelay: Boolean = false,首次创建调用resume(true)跳过延迟即可

内容的提问来源于stack exchange,提问作者BroscR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 13:57:19