Room数据库存储延迟:秒表应用圈速显示异常求助
秒表应用圈速记录问题排查
问题现象
开发的秒表应用基础计时功能正常,但圈速记录存在两个异常:
- 第一个圈速仅显示圈数,
overallTime和lapTime字段为空 - 后续圈速记录的时间比实际计时少约4秒(例如实际7秒时记录的圈速显示为3秒)
相关代码
Main Activity
class MainActivity : ComponentActivity() { private val stopWatchDatabase by lazy { Room.databaseBuilder( applicationContext, StopWatchDatabase::class.java, name = "object.database" ).build() } private val viewModel by viewModels<StopWatchViewModel> ( factoryProducer = { object: ViewModelProvider.Factory{ override fun <T: ViewModel> create(modelClass: Class<T>): T{ return StopWatchViewModel((stopWatchDatabase)) as T } } } ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { // Time var milliseconds by remember { mutableIntStateOf(0) } var seconds by remember { mutableIntStateOf(0) } var minutes by remember { mutableIntStateOf(0) } // Lap Time var lapMilliseconds by remember { mutableIntStateOf(0) } var lapSeconds by remember { mutableIntStateOf(0) } var lapMinutes by remember { mutableIntStateOf(0) } // Time Display var overallTime by remember { mutableStateOf("") } var lapTime by remember { mutableStateOf("") } // Timer Active States var isTimerActive by remember { mutableStateOf(false) } var isLapTimerActive by remember { mutableStateOf(false) } // Lap List var laps by remember { mutableStateOf(listOf<Lap>()) } // Instance Of Lap Entity val newLap = Lap( lapNumber = laps.size + 1, overallTime = overallTime, lapTime = lapTime ) // Function To Retrieve Lap List viewModel.retrieveAllLaps().observe(LocalLifecycleOwner.current) { laps = it } // Start Timer Launched Effect LaunchedEffect(isTimerActive) { while (isTimerActive && milliseconds < 100) { delay(1) milliseconds++ while (milliseconds == 100) { seconds += 1 milliseconds = 0 while (seconds == 60) { minutes += 1 seconds = 0 milliseconds = 0 } } } } // Start Lap Timer Launched Effect LaunchedEffect(isTimerActive && isLapTimerActive) { while (isTimerActive && isLapTimerActive && lapMilliseconds < 100) { delay(1) lapMilliseconds++ while (lapMilliseconds == 100) { lapSeconds += 1 lapMilliseconds = 0 while (lapSeconds == 60) { lapMinutes += 1 lapSeconds = 0 lapMilliseconds = 0 } } } } // App Layout Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier .fillMaxSize() ) { // Time Display Text( text = "%02d:%02d:%02d".format(minutes, seconds, milliseconds), fontSize = 50.sp ) if (isLapTimerActive) { // Lap Time Display Text( text = "%02d:%02d:%02d".format(lapMinutes, lapSeconds, lapMilliseconds),) } // Button To Start Timer Button(onClick = { isTimerActive = !isTimerActive }) { Text(text = "Start") } // Button To Reset Timer Button(onClick = { CoroutineScope(Dispatchers.IO).launch { isTimerActive = false isLapTimerActive = false viewModel.deleteAllLaps() delay(1) milliseconds = 0 seconds = 0 minutes = 0 lapMilliseconds = 0 lapSeconds = 0 lapMinutes = 0 overallTime = "" lapTime = "" }}) { Text(text = "Reset") } // Button To Lap Timer Button(onClick = { overallTime = "%02d:%02d:%02d".format(minutes, seconds, milliseconds) lapTime = "%02d:%02d:%02d".format(lapMinutes, lapSeconds, lapMilliseconds) viewModel.insertLap(newLap) lapMilliseconds = 0 lapSeconds = 0 lapMinutes = 0 isLapTimerActive = true }) { Text(text = "Lap") } // Lap List LazyColumn( verticalArrangement = Arrangement.spacedBy(30.dp) ) { items(laps) { lap -> Row( horizontalArrangement = Arrangement.spacedBy(30.dp) ) { Text(text = lap.lapNumber.toString()) Text(text = lap.lapTime) Text(text = lap.overallTime) } } } } } } }
Lap Entity
@Entity data class Lap( val lapNumber: Int?, val overallTime: String, var lapTime: String, @PrimaryKey(autoGenerate = true) val id: Int = 0 )
StopWatch Dao
@Dao interface StopWatchDao { @Upsert suspend fun insertLap(lap: Lap) @Query("DELETE FROM Lap") suspend fun deleteAllLaps() @Query("SELECT * FROM Lap") fun retrieveAllLaps(): Flow<List<Lap>> }
StopWatch Database
@Database( entities = [Lap::class], version = 1 ) abstract class StopWatchDatabase: RoomDatabase() { abstract val stopWatchDao: StopWatchDao }
StopWatch ViewModel
class StopWatchViewModel(private val stopWatchDatabase: StopWatchDatabase): ViewModel() { fun insertLap(lap: Lap){ viewModelScope.launch { stopWatchDatabase.stopWatchDao.insertLap(lap) } } fun deleteAllLaps(){ viewModelScope.launch { stopWatchDatabase.stopWatchDao.deleteAllLaps() } } fun retrieveAllLaps() = stopWatchDatabase.stopWatchDao.retrieveAllLaps().asLiveData(viewModelScope.coroutineContext) }
已尝试的解决方法
将insertLap函数放入协程并添加延迟,问题未解决。
问题根源与修复方案
1. 第一个圈速为空的根源
newLap是在Composable初始化时创建的,此时overallTime和lapTime为空字符串。点击Lap按钮时,插入的是这个初始实例,而非更新后的时间值。
修复:在点击Lap按钮时实时创建Lap实例,确保使用最新的时间:
// Button To Lap Timer Button(onClick = { val currentOverall = "%02d:%02d:%02d".format(minutes, seconds, milliseconds) val currentLap = "%02d:%02d:%02d".format(lapMinutes, lapSeconds, lapMilliseconds) val newLap = Lap( lapNumber = laps.size + 1, overallTime = currentOverall, lapTime = currentLap ) viewModel.insertLap(newLap) lapMilliseconds = 0 lapSeconds = 0 lapMinutes = 0 isLapTimerActive = true }) { Text(text = "Lap") }
2. 时间偏差的根源
- 嵌套的
while循环导致协程阻塞:当milliseconds达到100时,内层while循环直接累加秒和分钟,跳过了delay(1),导致计时速度远快于真实时间。 - 圈速计时初始未激活:第一次点击Lap才开启圈速计时,导致第一个圈速无计时数据,后续圈速起点偏差。
修复:
修正计时逻辑,替换嵌套while为if判断:
// Start Timer Launched Effect LaunchedEffect(isTimerActive) { while (isTimerActive) { delay(10) // 用10ms延迟更符合系统调度精度,避免1ms延迟的累积误差 milliseconds += 1 if (milliseconds == 100) { seconds += 1 milliseconds = 0 if (seconds == 60) { minutes += 1 seconds = 0 } } } } // Start Lap Timer Launched Effect LaunchedEffect(isTimerActive, isLapTimerActive) { while (isTimerActive && isLapTimerActive) { delay(10) lapMilliseconds += 1 if (lapMilliseconds == 100) { lapSeconds += 1 lapMilliseconds = 0 if (lapSeconds == 60) { lapMinutes += 1 lapSeconds = 0 } } } }
启动主计时时同步激活圈速计时:
// Button To Start Timer Button(onClick = { isTimerActive = !isTimerActive if (isTimerActive) { isLapTimerActive = true } }) { Text(text = if (isTimerActive) "Pause" else "Start") }
3. Reset按钮的冗余协程修复
Reset按钮中无需创建新的CoroutineScope,直接重置状态即可:
// Button To Reset Timer Button(onClick = { isTimerActive = false isLapTimerActive = false viewModel.deleteAllLaps() milliseconds = 0 seconds = 0 minutes = 0 lapMilliseconds = 0 lapSeconds = 0 lapMinutes = 0 }) { Text(text = "Reset") }
内容的提问来源于stack exchange,提问作者Bob Rasner
相关产品推荐
相关产品推荐

