在viewModelScope.launch中执行代码致App崩溃,求CameraX计时方案
CameraX录像计时器崩溃问题解决
问题描述
我正尝试为CameraX录像应用实现一个计时器,要求录像时每秒更新TextView。以下是Activity调用的ViewModel中的启动和停止方法:
fun startTimer(timerText: TextView) { timer = Timer() viewModelScope.launch(Dispatchers.IO) { timer?.scheduleAtFixedRate(timerTask { timeCount += 1u val tempTimerText = (timeCount / 60u).toString().padStart(2, '0') + ":" + (timeCount % 60u).toString().padStart(2, '0') timerText.text = tempTimerText }, 1000, 1000) } } fun stopTimer(timerText: TextView) { if (timer != null) { timer?.cancel() timer?.purge() timerText.text = application.getString(R.string.video_timer) } }
每次调用该方法应用都会崩溃,我哪里出错了?如何在不阻塞UI和录像的情况下在后台执行计时器并更新TextView?
更新内容
按照建议实现了LiveData观察者,但问题依旧,应用崩溃且Logcat无信息。
ViewModel代码:
fun startTimer(initial: String): MutableLiveData<String> { timer = Timer() timerCounterText.value = initial viewModelScope.launch(Dispatchers.IO) { timer?.scheduleAtFixedRate(timerTask { timerCounter += 1u timerCounterText.value = (timerCounter / 60u).toString().padStart(2, '0') + ":" + (timerCounter % 60u).toString().padStart(2, '0') }, 1000, 1000) } return timerCounterText }
Activity订阅代码:
cameraViewModel.startTimer().observe(this) { timeCount -> timerText.text = timeCount }
问题分析与解决方案
原始代码崩溃原因
- 后台线程操作UI控件:
timerText.text = tempTimerText在timerTask的后台线程执行,Android要求所有UI操作必须在主线程完成,直接触发CalledFromWrongThreadException导致崩溃。 - ViewModel持有UI引用:将
TextView传入ViewModel违反MVVM架构原则,ViewModel不应持有UI层引用,会引发内存泄漏和生命周期异常。
更新后代码仍崩溃的原因
TimerTask运行在后台线程,而MutableLiveData.setValue()必须在主线程调用,后台线程调用该方法会抛出未捕获异常,导致应用崩溃。需改用postValue(),它能在后台线程安全地将更新任务投递到主线程执行。
正确实现方案
1. ViewModel层实现
class CameraViewModel(application: Application) : AndroidViewModel(application) { private val _timerCounterText = MutableLiveData<String>() val timerCounterText: LiveData<String> = _timerCounterText // 对外暴露不可变LiveData private var timer: Timer? = null private var timerCounter: UInt = 0u fun startTimer(initialText: String = "00:00") { timerCounter = 0u _timerCounterText.postValue(initialText) timer?.cancel() // 先取消已有定时器,避免重复启动 timer = Timer() timer?.scheduleAtFixedRate(object : TimerTask() { override fun run() { timerCounter += 1u val timeStr = String.format("%02d:%02d", timerCounter / 60u, timerCounter % 60u ) _timerCounterText.postValue(timeStr) // 后台线程用postValue更新LiveData } }, 1000, 1000) } fun stopTimer() { timer?.cancel() timer?.purge() timer = null _timerCounterText.postValue(getApplication<Application>().getString(R.string.video_timer)) } override fun onCleared() { super.onCleared() timer?.cancel() // ViewModel销毁时取消定时器,防止内存泄漏 } }
2. Activity层订阅
// 在Activity生命周期方法(如onCreate)中订阅 cameraViewModel.timerCounterText.observe(this) { timeStr -> timerText.text = timeStr } // 启动录像时调用 cameraViewModel.startTimer() // 停止录像时调用 cameraViewModel.stopTimer()
关键优化点
- 使用
postValue()更新LiveData:后台线程中必须通过postValue()通知UI更新,确保操作在主线程执行。 - ViewModel与UI解耦:通过LiveData传递数据,ViewModel不持有任何UI控件引用,符合MVVM架构规范。
- 定时器生命周期管理:启动新定时器前取消旧实例,ViewModel销毁时清理定时器,避免内存泄漏和重复任务。
- 简化时间格式化:用
String.format替代字符串拼接,代码更简洁易维护。
内容的提问来源于stack exchange,提问作者AashishKSahu
相关产品推荐
相关产品推荐

