Android kotlin 定时n秒完成时回调,含暂停和继续

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()
    }
}
相关推荐
Dovis(誓平步青云)3 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码5 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程6 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
事圆则缓7 小时前
Kotlin 入门与面试:从空安全、扩展函数到协程
安全·面试·kotlin
传奇开心果编程8 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程10 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程10 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程10 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓10 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程10 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer