Kotlin
object TimerTaskUtil {
// 协程任务对象,控制定时任务生命周期
private var timerJob: Job? = null
// 定时任务总时长(毫秒)- 初始化时赋值,全程不变
private var totalDelayMs: Long = 0
// 已执行的计时时长(毫秒)- 暂停时累加,恢复时基于此计算剩余时长
private var elapsedMs: Long = 0
// 计时开始时间戳(毫秒)- 用于计算单次运行的已耗时
private var startTime: Long = 0
// 计时状态流 - 对外暴露不可变状态,支持外部监听计时状态变化
private val _timerState = MutableStateFlow(TimerState.IDLE)
val timerState: StateFlow<TimerState> = _timerState.asStateFlow()
/**
* 计时状态枚举
* IDLE:空闲(未启动/已完成/已取消)
* RUNNING:运行中
* PAUSED:已暂停
*/
enum class TimerState {
IDLE, RUNNING, PAUSED
}
/**
* 启动定时任务(重置所有状态,重新开始计时)
* @param delaySeconds 延迟执行的秒数(定时总时长)
* @param onFinish 任务结束后的回调(主线程执行,可直接更新UI)
*/
fun startTimer(delaySeconds: Long, onFinish: () -> Unit) {
// 取消已有任务,重置所有计时状态
cancelTimer()
totalDelayMs = TimeUnit.SECONDS.toMillis(delaySeconds)
elapsedMs = 0
startTime = System.currentTimeMillis()
_timerState.value = TimerState.RUNNING
// 启动新的协程任务(IO作用域不阻塞主线程)
timerJob = CoroutineScope(Dispatchers.IO).launch {
// 延迟剩余未执行的时长
delay(totalDelayMs - elapsedMs)
// 计时完成,重置状态并执行主线程回调
launch(Dispatchers.Main) {
_timerState.value = TimerState.IDLE
onFinish()
}
}
}
/**
* 暂停计时任务(仅在运行中时生效)
* 暂停后保留当前计时进度,可通过resumeTimer恢复
*/
fun pauseTimer() {
if (_timerState.value != TimerState.RUNNING) return
// 取消当前协程任务,停止计时
timerJob?.cancel()
// 计算本次运行的耗时,累加到总已耗时
val currentTime = System.currentTimeMillis()
elapsedMs += currentTime - startTime
// 更新计时状态为暂停
_timerState.value = TimerState.PAUSED
}
/**
* 恢复计时任务(仅在暂停时生效)
* 基于暂停前的进度,继续完成剩余计时
*/
fun resumeTimer() {
if (_timerState.value != TimerState.PAUSED) return
// 重置开始时间戳,启动新的协程任务
startTime = System.currentTimeMillis()
_timerState.value = TimerState.RUNNING
timerJob = CoroutineScope(Dispatchers.IO).launch {
// 延迟剩余未执行的时长(总时长 - 已耗时)
delay(totalDelayMs - elapsedMs)
// 计时完成,重置状态并执行主线程回调
launch(Dispatchers.Main) {
_timerState.value = TimerState.IDLE
}
}
}
/**
* 取消当前定时任务(清空所有状态,回到空闲)
* 页面销毁/不再需要计时时调用,避免内存泄漏
*/
fun cancelTimer() {
// 取消协程任务
timerJob?.cancel()
// 重置所有计时状态
timerJob = null
totalDelayMs = 0
elapsedMs = 0
startTime = 0
_timerState.value = TimerState.IDLE
}
/**
* 兼容原有方法:判断定时任务是否正在执行
*/
fun isTimerRunning(): Boolean {
return _timerState.value == TimerState.RUNNING
}
/**
* 扩展方法:获取当前剩余计时时长(秒,向下取整)
* 可用于UI展示剩余时间
*/
fun getRemainingSeconds(): Long {
if (_timerState.value == TimerState.IDLE) return 0
// 计算剩余毫秒数,转换为秒
val remainingMs = totalDelayMs - elapsedMs
return if (_timerState.value == TimerState.RUNNING) {
// 运行中:实时计算剩余时长(总剩余 - 本次已运行时长)
val currentRunMs = System.currentTimeMillis() - startTime
Math.max(0, (remainingMs - currentRunMs) / 1000)
} else {
// 暂停/空闲:直接返回固定剩余时长
Math.max(0, remainingMs / 1000)
}
}
}
使用
Kotlin
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var tvRemaining: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvRemaining = findViewById(R.id.tv_remaining_time)
// 启动10秒定时任务
TimerTaskUtil.startTimer(10) {
tvRemaining.text = "计时完成!"
}
// 每秒刷新一次剩余时间展示
lifecycleScope.launch {
while (isActive) { // 核心:依托lifecycleScope的isActive,页面销毁时协程自动取消
// 仅当页面处于「可见可交互」状态(STARTED)时更新UI,后台时暂停刷新
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
val remaining = TimerTaskUtil.getRemainingSeconds()
LogUtil.d("剩余:$remaining 秒")
}
delay(1000) // 每秒刷新一次,非阻塞协程
}
}
}
// 页面销毁时取消定时任务,避免内存泄漏
override fun onDestroy() {
super.onDestroy()
TimerTaskUtil.cancelTimer()
}
}