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

如何用Kotlin协程实现CountDownTimer?解决Handler创建报错问题

解决安卓CountDownTimer协程启动的Looper错误及协程替代实现

错误原因

Android原生CountDownTimer内部依赖Handler工作,而Handler必须在关联了Looper的线程中创建。你使用CoroutineScope(Dispatchers.Default)启动协程,该调度器的工作线程未初始化Looper,因此创建CountDownTimer时会抛出Can't create handler inside thread...异常。


方案1:修复原有CountDownTimer调用

CountDownTimer的onTick和onFinish回调本身就在主线程执行,完全不需要在后台线程启动它。直接调整调用逻辑即可:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    btnStartPause.setOnClickListener {
        if (isRunning) {
            pauseTimer()
        } else {
            val time = edtTxtTimer.text.toString().trim()
            timeInMilliSeconds = time.toLong() * 60000L // 1m = 60,000ms
            // 直接在主线程调用startTimer,无需后台协程
            startTimer(timeInMilliSeconds)
        }
    }
}

// 原startTimer方法无需修改
private fun startTimer(time_in_milli_second: Long) {
    countdown_timer = object : CountDownTimer(time_in_milli_second, 1000) {
        override fun onTick(millisUntilFinished: Long) {
            timeInMilliSeconds = millisUntilFinished
            updateUI()
        }

        override fun onFinish() {
            btnStartPause.text = "Start"
            loadConfeti()
        }
    }
    countdown_timer.start()

    isRunning = true
    btnStartPause.text = "Pause"
    btnReset.visibility = View.GONE
}

如果一定要用协程包裹,只需指定主线程调度器:

CoroutineScope(Dispatchers.Main).launch {
    startTimer(timeInMilliSeconds)
}

方案2:用Kotlin协程实现计时器

完全用协程替代原生CountDownTimer,通过delay实现计时,用Job管理暂停/取消逻辑:

private var timerJob: Job? = null
private var remainingTime: Long = 0
private var isRunning = false

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    btnStartPause.setOnClickListener {
        if (isRunning) {
            pauseTimer()
        } else {
            val time = edtTxtTimer.text.toString().trim()
            remainingTime = time.toLong() * 60000L
            startCoroutineTimer()
        }
    }
}

private fun startCoroutineTimer() {
    isRunning = true
    btnStartPause.text = "Pause"
    btnReset.visibility = View.GONE

    timerJob = CoroutineScope(Dispatchers.Main).launch {
        while (remainingTime > 0) {
            delay(1000)
            remainingTime -= 1000
            updateUI() // 直接在主线程更新UI
        }
        // 计时结束
        btnStartPause.text = "Start"
        loadConfeti()
        isRunning = false
    }
}

private fun pauseTimer() {
    timerJob?.cancel()
    isRunning = false
    btnStartPause.text = "Start"
    btnReset.visibility = View.VISIBLE
}

// 页面销毁时取消协程,避免内存泄漏
override fun onDestroy() {
    super.onDestroy()
    timerJob?.cancel()
}

优势

  • 无需依赖Handler和Looper,逻辑更简洁
  • 协程的Job可以灵活控制计时器的暂停、取消
  • 直接在主线程调度器中执行,更新UI无需额外线程切换

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 16:10:36