Kotlin 协程与 ViewModel 的完美结合

发布于:2025-06-13 ⋅ 阅读:(21) ⋅ 点赞:(0)

在 Android 开发中,Kotlin 协程与 ViewModel 的结合是现代应用架构的核心。这种组合提供了高效、简洁的异步处理解决方案,同时保持代码的清晰和可维护性。

核心优势

  • 生命周期感知:协程在 ViewModel 作用域内自动取消,避免内存泄漏

  • 简化异步代码:用同步方式编写异步逻辑

  • 错误处理:统一异常处理机制

  • 线程管理:轻松切换线程上下文

基础实现

1. 添加依赖

dependencies {
    // ViewModel 和协程支持
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
}

2. 创建协程支持的 ViewModel

class UserViewModel : ViewModel() {
    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users: StateFlow<List<User>> = _users

    private val _loading = MutableStateFlow(false)
    val loading: StateFlow<Boolean> = _loading

    private val _error = MutableSharedFlow<String>()
    val error: SharedFlow<String> = _error

    // 使用 viewModelScope 启动协程
    fun loadUsers() {
        viewModelScope.launch {
            try {
                _loading.value = true
                val result = userRepository.getUsers() // 挂起函数
                _users.value = result
            } catch (e: Exception) {
                _error.emit("加载失败: ${e.message}")
            } finally {
                _loading.value = false
            }
        }
    }
}

进阶用法

1. 结合 Flow 使用

class DataViewModel : ViewModel() {
    private val _searchQuery = MutableStateFlow("")
    val searchResults: StateFlow<List<Item>> = _searchQuery
        .debounce(300) // 防抖处理
        .distinctUntilChanged()
        .filter { it.length > 2 }
        .flatMapLatest { query ->
            flow {
                emit(emptyList()) // 显示加载状态
                emit(repository.searchItems(query))
            }
        }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = emptyList()
        )
    
    fun search(query: String) {
        _searchQuery.value = query
    }
}

2. 并行处理

fun loadUserDetails(userId: String) {
    viewModelScope.launch {
        _loading.value = true
        
        // 并行执行多个请求
        val userDeferred = async { userRepo.getUser(userId) }
        val postsDeferred = async { postRepo.getPosts(userId) }
        val friendsDeferred = async { friendRepo.getFriends(userId) }
        
        try {
            val user = userDeferred.await()
            val posts = postsDeferred.await()
            val friends = friendsDeferred.await()
            
            _userDetails.value = UserDetails(user, posts, friends)
        } catch (e: Exception) {
            _error.emit("加载详情失败")
        } finally {
            _loading.value = false
        }
    }
}

3. 超时处理

kotlin

fun fetchDataWithTimeout() {
    viewModelScope.launch {
        try {
            val result = withTimeout(10_000) { // 10秒超时
                apiService.fetchData()
            }
            _data.value = result
        } catch (e: TimeoutCancellationException) {
            _error.emit("请求超时")
        }
    }
}

最佳实践

1. 分离业务逻辑

// UserViewModel.kt
class UserViewModel(private val userRepo: UserRepository) : ViewModel() {
    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users: StateFlow<List<User>> = _users
    
    fun loadUsers() {
        viewModelScope.launch {
            _users.value = userRepo.getUsers()
        }
    }
}

// UserRepository.kt
class UserRepository(private val api: UserApi) {
    suspend fun getUsers(): List<User> {
        return api.getUsers().mapToDomain()
    }
}

2. 错误处理封装

// 扩展函数简化错误处理
fun <T> ViewModel.launchWithErrorHandling(
    onError: (Throwable) -> Unit = { _ -> },
    block: suspend () -> T
) {
    viewModelScope.launch {
        try {
            block()
        } catch (e: Exception) {
            onError(e)
        }
    }
}

// 使用示例
fun loadData() {
    launchWithErrorHandling(
        onError = { e -> _error.emit("Error: ${e.message}") }
    ) {
        _data.value = repository.fetchData()
    }
}

3. 测试策略

@OptIn(ExperimentalCoroutinesApi::class)
class UserViewModelTest {
    @get:Rule
    val rule = InstantTaskExecutorRule()
    
    private lateinit var viewModel: UserViewModel
    private val testDispatcher = StandardTestDispatcher()
    
    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)
        viewModel = UserViewModel(FakeUserRepository())
    }
    
    @After
    fun tearDown() {
        Dispatchers.resetMain()
    }
    
    @Test
    fun `loadUsers should update state`() = runTest {
        viewModel.loadUsers()
        testDispatcher.scheduler.advanceUntilIdle()
        
        assertEquals(3, viewModel.users.value.size)
    }
}

常见问题解决

1. 协程取消问题

fun startLongRunningTask() {
    viewModelScope.launch {
        // 定期检查协程是否活跃
        for (i in 1..100) {
            ensureActive() // 如果协程被取消则抛出异常
            // 或者手动检查
            if (!isActive) return@launch
            
            // 执行任务
            delay(1000)
        }
    }
}

2. 冷流与热流选择

场景 推荐选择 说明
一次性数据 StateFlow 简单状态管理
事件处理 SharedFlow 避免事件丢失
复杂数据流 Flow 冷流,按需执行
响应式UI StateFlow/Flow 结合Lifecycle

3. 生命周期处理

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        lifecycleScope.launch {
            // 当Activity在后台时暂停收集
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.users.collect { users ->
                    updateUI(users)
                }
            }
        }
    }
}

性能优化技巧

  1. 使用缓冲

    val events = MutableSharedFlow<Event>(
        extraBufferCapacity = 64,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
  2. 避免重复启动协程

    private var searchJob: Job? = null
    
    fun search(query: String) {
        searchJob?.cancel()
        searchJob = viewModelScope.launch {
            // 执行搜索
        }
    }

  3. 使用协程调度器优化

    viewModelScope.launch(Dispatchers.Default) {
        // CPU密集型操作
    }
    
    viewModelScope.launch(Dispatchers.IO) {
        // IO操作
    }
    
    viewModelScope.launch(Dispatchers.Main.immediate) {
        // UI更新
    }

总结

Kotlin 协程与 ViewModel 的结合为 Android 开发提供了强大的异步处理能力:

  • 使用 viewModelScope 确保协程生命周期与 ViewModel 一致

  • 结合 StateFlow/SharedFlow 实现响应式 UI

  • 遵循单一职责原则,分离业务逻辑

  • 采用结构化并发简化错误处理和资源管理

掌握这些技术将使你能够构建更加健壮、高效且易于维护的 Android 应用程序。