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 老项目迁移系列,持续更新中。

相关推荐
带刺的坐椅7 分钟前
Hot-Plug 实战:运行期管理 Solon 插件
java·solon·插件·热插拔
大黄说说8 分钟前
SQL Server 执行计划怎么看?快速定位 SQL 慢的根源
java·linux·数据库
雪之下雪乃的代码日记1 小时前
Python快速入门(Java开发者版)
java·开发语言·笔记·python
2501_915909061 小时前
全面解析iOS应用上架到App Store的完整流程与关键注意事项
android·macos·ios·小程序·uni-app·cocoa·iphone
敲个大西瓜1 小时前
Springboot核心面试题
java·spring boot·后端
器灵科技1 小时前
Seedance2.5 VS MiniMax H3 同日上线:AI短剧创作者该怎么选?
java·人工智能·阿里云·prompt·aigc
xbgRS1 小时前
Mybatis源码-核心流程及核心类
java·mybatis
zzh___zzh1 小时前
Java Stream API 常用方法笔记
java·笔记
萌动的小火苗2 小时前
1、python基础面试题
java·开发语言·python
麻雀聊技术2 小时前
一重启 AI 就失忆?用 Spring AI + PostgreSQL 3步搞定“长期记忆”持久化(附完整代码)
java·spring·openai