如何用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
相关产品推荐
相关产品推荐

