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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 22:34:49