You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android-Kotlin-Jetpack Compose:实现锁屏/后台持续运行的OTP倒计时器

解决OTP倒计时锁屏后重置的问题

核心方案:基于时间戳的倒计时计算

放弃依赖持续运行的计时器(比如CountDownTimer)维护剩余时间,改为存储倒计时的绝对结束时间戳,每次UI需要更新时,通过当前时间和结束时间的差值计算剩余时长。这样即使App被挂起、锁屏,只要回到前台,就能直接算出最新的剩余时间,无需后台持续运行计时器。

步骤1:ViewModel中存储结束时间戳

在ViewModel里保存倒计时的结束时间(用Long类型),而非剩余秒数。同时提供启动倒计时的方法,计算并保存结束时间:

class OtpViewModel : ViewModel() {
    // 用MutableState保存结束时间戳,0表示未启动或已结束
    private val _countdownEndTime = mutableStateOf<Long>(0)
    val countdownEndTime: State<Long> = _countdownEndTime

    // 启动倒计时,参数为总时长(比如60秒)
    fun startCountdown(totalSeconds: Long) {
        val currentTime = System.currentTimeMillis()
        _countdownEndTime.value = currentTime + (totalSeconds * 1000)
    }

    // 计算当前剩余秒数
    fun getRemainingSeconds(): Long {
        val currentTime = System.currentTimeMillis()
        val remaining = (_countdownEndTime.value - currentTime) / 1000
        return if (remaining > 0) remaining else 0
    }
}

步骤2:Composable中实时计算并显示剩余时间

在UI层,用produceState自动监听时间变化,每秒刷新一次剩余时间:

@Composable
fun OtpCountdown(viewModel: OtpViewModel) {
    // 自动计算剩余时间,每秒刷新一次,倒计时结束后停止
    val remainingSeconds by produceState(initialValue = 0L) {
        while (true) {
            value = viewModel.getRemainingSeconds()
            if (value == 0L) break
            delay(1000)
        }
    }

    Text(
        text = if (remainingSeconds > 0) "剩余${remainingSeconds}秒" else "重新获取",
        modifier = Modifier.padding(16.dp)
    )
}

步骤3:处理App前后台切换

当App从后台回到前台时,produceState会自动重新调用getRemainingSeconds(),基于当前系统时间和存储的结束时间戳计算最新剩余时间,锁屏再解锁后显示的数值会自动更新为正确值,不会重置。

额外优化:应对进程被杀死的情况

如果App进程被系统回收,ViewModel中的结束时间会丢失。可以把结束时间戳存在DataStore或SharedPreferences中,ViewModel初始化时先从本地存储读取:

class OtpViewModel(private val dataStore: DataStore<Preferences>) : ViewModel() {
    private val COUNTDOWN_END_TIME_KEY = stringPreferencesKey("countdown_end_time")

    init {
        viewModelScope.launch {
            dataStore.data.collect { preferences ->
                _countdownEndTime.value = preferences[COUNTDOWN_END_TIME_KEY] ?: 0L
            }
        }
    }

    fun startCountdown(totalSeconds: Long) {
        val currentTime = System.currentTimeMillis()
        val endTime = currentTime + (totalSeconds * 1000)
        _countdownEndTime.value = endTime
        viewModelScope.launch {
            dataStore.edit { preferences ->
                preferences[COUNTDOWN_END_TIME_KEY] = endTime
            }
        }
    }

    // 其他方法不变
}

方案优势

  • 无需后台持续运行计时器,避免系统挂起App导致的计时器中断问题
  • 所有时间计算基于系统绝对时间,App在任何状态下回到前台都能显示准确剩余时间
  • 本地存储优化可应对进程被回收的极端场景

内容的提问来源于stack exchange,提问作者Harish Padmanabh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 22:39:19