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()
    }
}
相关推荐
AD钙奶-lalala19 分钟前
Android Studio新建项目默认不使用Compose模版
android·ide·android studio
故渊at2 小时前
第一板块:Android 系统基石与运行原理 | 第二篇:Android 编译、打包与安装机制
android·系统架构·apk·打包·application·dalvik·android编译
故渊at3 小时前
第一板块:Android 系统基石与运行原理 | 第三篇:ART 与 Dalvik 运行时环境原理
android·对象模型·内存布局·运行原理·art·dalvik
私人珍藏库4 小时前
【Android】Wallcraft 3.62.0-最强4 K壁纸软件-解锁高级版
android·智能手机·app·工具·软件·多功能
GesLuck4 小时前
Node-RED企业微信发送—群文件
android·java·企业微信
whatever who cares5 小时前
android中fragment demo举例
android·java·开发语言
zhangphil5 小时前
Android将ImageView显示的图原样取出转换为Bitmap,Kotlin
android·kotlin
plainGeekDev5 小时前
CountDownTimer → Flow
android·java·kotlin
仙俊红5 小时前
如何优化 MySQL 深分页 SQL
android·sql·mysql
awu的Android笔记5 小时前
网络闪断 + DNS 故障:Android弱网模拟中最容易被忽视的两个场景
android·tcp/ip