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 天前
鸿蒙应用开发实战【94】— 实战多卡号场景管理最佳实践
android·华为·harmonyos·鸿蒙系统
用户41659673693551 天前
视频源文件音频轨异常分析记录
android
Memory_荒年1 天前
WebSocket:让服务器学会“主动搭讪”的黑科技
java
Henrii_历小海1 天前
WhatsApp Business API 2026 计费架构重构:从“对话计费“到“按消息计费“的技术实现与工程应对
java·重构·架构
炸薯条!1 天前
从零开始学C++ (内存管理)
java·jvm·c++
许彰午1 天前
100_Python面试常见问题汇总
java·python·面试
weixin_727535621 天前
Spring @Transactional 事务失效:原理层面深度解析
java·spring
没钥匙的锁11 天前
16-Java反射机制:Spring IOC背后的核心技术
java·开发语言·spring
geinvse_seg1 天前
飞算JavaAI:把企业采购协同需求拆成一套可运行的慧采平台
java·ai·飞算
雨白1 天前
嵌套 ScrollView 滑动冲突实战:实现内部优先滚动
android