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

Jetpack Compose中如何在其他线程更新状态以触发重组?

问题:Compose中后台线程更新MutableState导致崩溃,如何正确实现定时更新时间?

我用一个简单的Text组件显示当前时间,尝试启动周期性Timer Task时应用崩溃,提示“APP STOPPED WORKING”。请问如何在其他线程中定期更新var currentTime by mutableStateOf("12:00"),既能正确触发重组又不会崩溃?

代码示例

class MyViewModel : ViewModel() {
    var currentTime by mutableStateOf("12:00")

    init {
        val clockTimer = Timer()
        clockTimer.scheduleAtFixedRate(object : TimerTask() {
            override fun run() {
                val hoursAndMinutes = SimpleDateFormat("HH:mm", Locale.US).format(Date())
                Log.d("DEBUG", "Current Time is: $hoursAndMinutes")
                currentTime = hoursAndMinutes
            }
         }, 0, 60000)
    }
}

@Preview
@Composable
fun ClockView(myViewModel: MyViewModel = viewModel()){
    Text(
        text = myViewModel.currentTime
    )
}

崩溃栈追踪(翻译后)

致命异常: Timer-0
进程: com.myapp.c, PID: 15437
    java.lang.IllegalStateException: 读取了在快照创建后生成的状态,或者读取了尚未应用的快照中的状态
        at androidx.compose.runtime.snapshots.SnapshotKt.readError(Snapshot.kt:1857)
        at androidx.compose.runtime.snapshots.SnapshotKt.current(Snapshot.kt:2110)
        at androidx.compose.runtime.SnapshotMutableStateImpl.setValue(SnapshotState.kt:299)
        at com.myapp.c.ui.MyViewModel.setCurrentTime(Test.kt:41)
        at com.myapp.c.ui.MyViewModel$1.run(Test.kt:25)
        at java.util.TimerThread.mainLoop(Timer.java:562)
        at java.util.TimerThread.run(Timer.java:512)

解决方案

崩溃原因

Compose的MutableState更新必须在主线程执行,而TimerTask的run()方法是在后台线程运行的,直接更新currentTime会触发Compose的线程检查,抛出IllegalStateException。

方法一:用ViewModelScope + 协程(推荐,符合Jetpack规范)

利用viewModelScope自带的主线程调度和生命周期绑定能力,无需手动管理线程和任务取消:

class MyViewModel : ViewModel() {
    var currentTime by mutableStateOf("12:00")

    init {
        viewModelScope.launch {
            while (true) {
                // 这里在主线程执行,可直接更新状态
                val hoursAndMinutes = SimpleDateFormat("HH:mm", Locale.US).format(Date())
                Log.d("DEBUG", "Current Time is: $hoursAndMinutes")
                currentTime = hoursAndMinutes
                delay(60000) // 每隔60秒执行一次
            }
        }
    }
}
  • viewModelScope会在ViewModel销毁时自动取消协程,避免内存泄漏;
  • 默认在主线程执行,符合Compose的状态更新要求。

方法二:TimerTask中切换到主线程更新状态

如果坚持用Timer,需要把状态更新操作切换到主线程:

class MyViewModel : ViewModel() {
    var currentTime by mutableStateOf("12:00")
    private val mainHandler = Handler(Looper.getMainLooper())
    private lateinit var clockTimer: Timer

    init {
        clockTimer = Timer()
        clockTimer.scheduleAtFixedRate(object : TimerTask() {
            override fun run() {
                val hoursAndMinutes = SimpleDateFormat("HH:mm", Locale.US).format(Date())
                Log.d("DEBUG", "Current Time is: $hoursAndMinutes")
                // 切换到主线程更新状态
                mainHandler.post {
                    currentTime = hoursAndMinutes
                }
            }
         }, 0, 60000)

        // ViewModel销毁时取消Timer,防止内存泄漏
        viewModelScope.registeredDisposable {
            clockTimer.cancel()
        }
    }
}

方法三:用StateFlow实现响应式更新(更优雅)

用StateFlow管理时间状态,自动处理线程切换和生命周期:

class MyViewModel : ViewModel() {
    val currentTime: StateFlow<String> = flow {
        while (true) {
            emit(SimpleDateFormat("HH:mm", Locale.US).format(Date()))
            delay(60000)
        }
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000), // 订阅者取消后5秒停止任务
        initialValue = "12:00"
    )
}

// Composable中使用
@Preview
@Composable
fun ClockView(myViewModel: MyViewModel = viewModel()){
    val currentTime by myViewModel.currentTime.collectAsState()
    Text(text = currentTime)
}
  • stateIn会将Flow转换为StateFlow,自动在主线程发射数据;
  • collectAsState会自动订阅状态变化并触发重组;
  • WhileSubscribed策略能在无订阅时暂停任务,节省资源。

额外注意事项

  • 替换SimpleDateFormat:SimpleDateFormat不是线程安全的,推荐使用Android 8.0+的DateTimeFormatter,或者引入ThreeTenABP库兼容低版本;
  • 所有Compose状态的更新操作必须在主线程执行,这是硬性约束。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 16:10:37