1. 为什么需要ViewModel处理定时任务?
在Android开发中,定时任务是一个常见需求,但直接在Activity或Fragment中实现会遇到生命周期管理的难题。当屏幕旋转或配置变更时,传统的Handler或Timer会被销毁重建,导致任务中断或内存泄漏。
ViewModel作为Android架构组件的一部分,设计初衷就是解决这类问题。它独立于UI控制器生命周期,仅在Activity真正finish时才会销毁。这意味着:
- 定时任务不会因配置变更而中断
- 数据可以安全保留在内存中
- 避免了因生命周期导致的回调异常
重要提示:虽然ViewModel适合管理定时任务,但长时间运行的后台任务仍应使用WorkManager等专用组件,ViewModel更适合处理与UI生命周期相关的定时逻辑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 实现ViewModel定时任务的三种方案
2.1 使用Handler + postDelayed
这是最基础的实现方式,适合简单的延迟操作:
kotlin复制class TimerViewModel : ViewModel() {
private val handler = Handler(Looper.getMainLooper())
private var runnable: Runnable? = null
fun startTimer(delay: Long, action: () -> Unit) {
runnable = Runnable {
action()
handler.postDelayed(this, delay)
}
handler.postDelayed(runnable!!, delay)
}
override fun onCleared() {
handler.removeCallbacks(runnable)
super.onCleared()
}
}
关键点:
- 必须保存Runnable引用以便取消
- onCleared()中必须移除回调
- 使用MainLooper确保UI线程安全
2.2 使用Coroutine + viewModelScope
Kotlin协程提供了更现代的解决方案:
kotlin复制class CoroutineTimerViewModel : ViewModel() {
private var job: Job? = null
fun startTimer(interval: Long, action: () -> Unit) {
job = viewModelScope.launch {
while (true) {
delay(interval)
action()
}
}
}
fun stopTimer() {
job?.cancel()
}
}
优势:
- 自动绑定ViewModel生命周期
- 结构化并发管理
- 可轻松切换调度线程
2.3 使用RxJava + autoDispose
对于RxJava项目可以这样实现:
kotlin复制class RxTimerViewModel : ViewModel() {
private val disposables = CompositeDisposable()
fun startTimer(interval: Long, action: () -> Unit) {
Observable.interval(interval, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.autoDispose(viewModelScope)
.subscribe { action() }
.addTo(disposables)
}
override fun onCleared() {
disposables.clear()
}
}
适用场景:
- 已有RxJava基础的项目
- 需要复杂响应式操作的场景
3. 定时任务中的常见陷阱与解决方案
3.1 内存泄漏防护
即使使用ViewModel也需注意:
- 静态Handler引用:避免在ViewModel中声明静态Handler
- 匿名内部类:匿名Runnable可能持有外部引用
- 生命周期回调:确保在onCleared()中释放资源
3.2 配置变更导致的任务重复
典型表现:旋转屏幕后定时任务加速
解决方案:
kotlin复制private var timerStarted = false
fun startTimerOnce(interval: Long) {
if (!timerStarted) {
// 启动逻辑
timerStarted = true
}
}
3.3 后台执行限制
Android 8.0+对后台服务有限制:
- 使用WorkManager处理长时间任务
- 前台服务通知必须显示
- 考虑使用AlarmManager精确唤醒
4. 高级应用场景实现
4.1 倒计时功能实现
kotlin复制class CountDownViewModel : ViewModel() {
private var remainingTime = MutableLiveData<Long>()
private var job: Job? = null
fun startCountDown(duration: Long) {
remainingTime.value = duration
job = viewModelScope.launch {
while (remainingTime.value ?: 0 > 0) {
delay(1000)
remainingTime.value = remainingTime.value?.minus(1000)
}
}
}
fun getRemainingTime(): LiveData<Long> = remainingTime
}
4.2 定时网络请求轮询
kotlin复制class PollingViewModel(
private val apiService: ApiService
) : ViewModel() {
private val pollingData = MutableLiveData<Result<Data>>()
fun startPolling(interval: Long) {
viewModelScope.launch {
while (true) {
try {
val result = withContext(Dispatchers.IO) {
apiService.fetchData()
}
pollingData.value = Result.success(result)
} catch (e: Exception) {
pollingData.value = Result.failure(e)
}
delay(interval)
}
}
}
}
4.3 多任务定时调度
kotlin复制class MultiTimerViewModel : ViewModel() {
private val timers = mutableMapOf<String, Job>()
fun startTimer(key: String, interval: Long, action: () -> Unit) {
stopTimer(key)
timers[key] = viewModelScope.launch {
while (true) {
delay(interval)
action()
}
}
}
fun stopTimer(key: String) {
timers[key]?.cancel()
}
fun stopAllTimers() {
timers.values.forEach { it.cancel() }
}
}
5. 性能优化与最佳实践
5.1 减少UI更新频率
对于高频定时器:
kotlin复制private var lastUpdateTime = 0L
fun startOptimizedTimer() {
viewModelScope.launch {
while (true) {
val currentTime = System.currentTimeMillis()
if (currentTime - lastUpdateTime > 1000) {
updateUI()
lastUpdateTime = currentTime
}
delay(100)
}
}
}
5.2 线程调度策略
- UI更新:Dispatchers.Main
- 计算密集型:Dispatchers.Default
- IO操作:Dispatchers.IO
kotlin复制viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
// 网络请求
}
withContext(Dispatchers.Main) {
// 更新UI
}
}
5.3 测试策略
单元测试示例:
kotlin复制@Test
fun testCountDown() = runTest {
val vm = CountDownViewModel()
vm.startCountDown(5000)
advanceTimeBy(1000)
assertEquals(4000, vm.getRemainingTime().value)
advanceTimeBy(4000)
assertEquals(0, vm.getRemainingTime().value)
}
测试技巧:
- 使用TestCoroutineDispatcher控制虚拟时间
- Mock网络请求响应
- 验证LiveData变化
6. 与其他架构组件的配合
6.1 结合LiveData自动更新UI
kotlin复制class TimerWithLiveDataViewModel : ViewModel() {
private val _elapsedTime = MutableLiveData<Long>(0)
val elapsedTime: LiveData<Long> = _elapsedTime
init {
viewModelScope.launch {
while (true) {
delay(1000)
_elapsedTime.value = (_elapsedTime.value ?: 0) + 1
}
}
}
}
6.2 使用SavedStateHandle恢复状态
kotlin复制class StatefulTimerViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
private val TIMER_KEY = "last_time"
val lastActiveTime = savedStateHandle.getLiveData<Long>(TIMER_KEY)
fun updateTimer() {
savedStateHandle[TIMER_KEY] = System.currentTimeMillis()
}
}
6.3 与Repository层整合
kotlin复制class DataSyncViewModel(
private val repository: DataRepository
) : ViewModel() {
private val _syncResult = MutableLiveData<SyncResult>()
val syncResult: LiveData<SyncResult> = _syncResult
fun startPeriodicSync(interval: Long) {
viewModelScope.launch {
while (true) {
try {
val result = repository.syncData()
_syncResult.value = SyncResult.Success(result)
} catch (e: Exception) {
_syncResult.value = SyncResult.Error(e)
}
delay(interval)
}
}
}
}
在实际项目中,我发现ViewModel处理定时任务最关键的还是生命周期管理。特别是在使用协程时,viewModelScope提供的自动取消机制能避免很多潜在问题。对于需要精确计时的场景,建议结合系统AlarmManager使用,而ViewModel更适合处理那些与UI展示相关的周期性操作。
