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

相关推荐
alexhilton31 分钟前
Android车载OS中的Remote Compose
android·kotlin·android jetpack
狼爷3 小时前
吃透 Java Function 接口,搞定 99% 的 Stream 场景
java·函数式编程
祎雪双十Gy7 小时前
从 DataX 的配置加载说起:我用 FastJson2 做了一个轻量级动态配置管理库
java·后端
小锋java12347 小时前
分享一套锋哥原创的SpringBoot4+Vue3宠物领养网站系统
java
考虑考虑10 小时前
Java实现hmacsha1加密算法
java·后端·java ee
落魄Android在线炒饭10 小时前
Android 自定义HAL开发篇之 HIDL篇——从入门到实战(上)
android
掉鱼的猫11 小时前
Spring Boot → Solon 注解迁移实战指南:一张对照表说清楚
java·spring boot
plainGeekDev11 小时前
广播接收器 → Flow + Lifecycle
android·java·kotlin
plainGeekDev11 小时前
EventBus → SharedFlow
android·java·kotlin
带刺的坐椅11 小时前
Spring Boot → Solon 注解迁移实战指南:一张对照表说清楚
java·springboot·web·solon