CountDownTimer → Flow

CountDownTimer → Flow

老写法(Java)

java 复制代码
new CountDownTimer(30000, 1000) {
    @Override
    public void onTick(long millisUntilFinished) {
        long seconds = millisUntilFinished / 1000;
        tvCountdown.setText("剩余 " + seconds + " 秒");
    }

    @Override
    public void onFinish() {
        tvCountdown.setText("完成");
        btnAction.setEnabled(true);
    }
}.start();

问题在哪里

CountDownTimer 不感知生命周期,Activity 退出后 tick 还在跑,回调会操作已销毁的 View 导致空指针。onTick 的精度也不保证精确的 1000ms,可能出现几毫秒到几十毫秒的漂移。

新写法(Kotlin + Flow)

kotlin 复制代码
// ViewModel 中
val countdownFlow: Flow<Int> = flow {
    for (i in 30 downTo 0) {
        emit(i)
        delay(1000)
    }
}

// Activity / Fragment 中
viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.countdownFlow.collect { seconds ->
            when (seconds) {
                0 -> {
                    tvCountdown.text = "完成"
                    btnAction.isEnabled = true
                }
                else -> tvCountdown.text = "剩余 $seconds 秒"
            }
        }
    }
}

或者直接在 ViewModel 里封装好状态:

kotlin 复制代码
class SmsViewModel : ViewModel() {

    private val _countdown = MutableStateFlow(60)
    val countdown: StateFlow<Int> = _countdown

    private var job: Job? = null

    fun startCountdown() {
        job?.cancel()
        job = viewModelScope.launch {
            for (i in 60 downTo 0) {
                _countdown.value = i
                delay(1000)
            }
        }
    }

    override fun onCleared() {
        job?.cancel()
        super.onCleared()
    }
}

一句话注意

viewLifecycleOwner.lifecycleScope 配合 repeatOnLifecycle 确保 Fragment 视图销毁后自动停止收集,ViewModel 里可以继续计时,但不会去更新已经销毁的 UI,完美避开 CountDownTimer 的泄漏问题。

如果倒计时对精度要求高(比如计时器),注意 delay 也有微小漂移,应记录起始时间戳,每次计算剩余时间:

kotlin 复制代码
flow {
    val start = System.currentTimeMillis()
    val total = 30_000L
    while (true) {
        val elapsed = System.currentTimeMillis() - start
        val remaining = ((total - elapsed) / 1000).toInt()
        if (remaining <= 0) { emit(0); break }
        emit(remaining)
        delay(1000)
    }
}

Java Android 老项目迁移系列,持续更新中。

相关推荐
君顾113 小时前
本地托管机构管理源码实战:需求拆解、数据建模与多端部署
java·开发语言·托管
青山木13 小时前
Hot 100 --- 最长有效括号
java·数据结构·算法·leetcode·动态规划
xcl092513 小时前
本地电竞服务交易系统架构设计与实战:从同城匹配到订单履约
java·spring boot
曹牧13 小时前
SVN 上查找文件
java
加贝哥|usun13 小时前
maven项目从外网搬迁至内网-本地仓库(不搭建私服)
java
paopaokaka_luck14 小时前
小学非遗科普平台(AI 非遗科普问答,ECharts学情数据、非遗资源分类与审核,资源收藏下载与学习记录,学习任务发布和作品评分,活动报名签到与统计)
java·人工智能·spring·信息可视化·数据分析·echarts
Android-Flutter14 小时前
android compose 知识点
android·compose
Yize.15 小时前
Spring Task定时任务全解析:从入门到实战
java·spring
其实防守也摸鱼15 小时前
Codex 下载与本地部署实战:从安装到运行全指南
android·大数据·运维·安全·自动化
ttyyttemo16 小时前
协程并发执行与共享状态
android