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

相关推荐
武子康1 小时前
Java-17 深入浅出MyBatis Mapper Proxy 源码解析:从 getMapper 到 invoke 的完整链路
java·后端
心之伊始1 小时前
Java 后端 AI 应用网关实战:多模型路由、Fallback、超时和可观测性设计
java·spring boot·大模型·架构设计·ai网关
仙俊红1 小时前
如何优化 MySQL 深分页 SQL
android·sql·mysql
小锋java12342 小时前
【技术专题】LangChain4j 开发Java Agent智能体 - 嵌入模型与向量数据库
java·人工智能
awu的Android笔记2 小时前
网络闪断 + DNS 故障:Android弱网模拟中最容易被忽视的两个场景
android·tcp/ip
程序员皮皮林2 小时前
Dubbo 的 SPI 和 JDK 的 SPI 有什么区别?
java·开发语言·dubbo
小锋java12342 小时前
10分钟学会Java16新特性record
java
是多巴胺不是尼古丁2 小时前
java‘期末复习--多态
java·开发语言
瑞雪兆丰年兮2 小时前
[从0开始学Java|第二十五天]项目阶段(综合练习&斗地主小游戏)
java·windows