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
相关产品推荐
相关产品推荐

