在 Android 开发中,Kotlin 协程是处理异步操作的首选方案,它能让异步代码更简洁、更易读。以下是 Android 协程异步编程的常用方法和模式:
一、基础构建块
1. launch
-
作用:启动一个新协程,不返回结果。
-
适用场景:执行不需要返回值的异步任务(如 UI 更新、启动后台操作)。
-
示例 :
viewModelScope.launch { // 默认在主线程执行 updateUI() // 切换到 IO 线程执行耗时操作 withContext(Dispatchers.IO) { saveDataToDisk() } }
2. async + await
-
作用 :异步执行任务并返回
Deferred
对象,通过await()
获取结果。 -
适用场景:并行执行多个异步任务,合并结果。
-
示例 :
viewModelScope.launch { val task1 = async(Dispatchers.IO) { fetchDataFromNetwork() } val task2 = async(Dispatchers.IO) { loadCacheFromDisk() } // 等待两个任务完成并合并结果 val result = combine(task1.await(), task2.await()) updateUI(result) }
3. withContext
-
作用:在指定调度器中执行代码块,并挂起当前协程,返回结果。
-
适用场景:切换线程执行耗时操作后回到原线程。
-
示例 :
viewModelScope.launch { // 主线程:显示加载状态 showLoading() // 切换到 IO 线程执行耗时操作 val data = withContext(Dispatchers.IO) { repository.fetchData() } // 自动回到主线程:更新 UI updateUI(data) }
二、调度器(Dispatchers)
调度器 | 作用 | 适用场景 |
---|---|---|
Dispatchers.Main |
主线程,用于 UI 操作 | 更新 TextView、启动动画 |
Dispatchers.IO |
后台线程,优化磁盘 / 网络 IO | 文件读写、网络请求 |
Dispatchers.Default |
后台线程,用于 CPU 密集型任务 | 复杂计算、JSON 解析 |
Dispatchers.Unconfined |
不指定固定线程,由调用者决定 | 测试、初期协程启动 |
三、取消与异常处理
1. 自动取消(ViewModelScope)
class MyViewModel : ViewModel() {
fun fetchData() {
// 使用 viewModelScope,Activity/Fragment 销毁时自动取消
viewModelScope.launch {
val data = withContext(Dispatchers.IO) {
repository.loadData()
}
_liveData.value = data
}
}
}
2. 手动取消协程
private var job: Job? = null
fun startTask() {
job = viewModelScope.launch {
// 执行耗时操作
}
}
fun cancelTask() {
job?.cancel() // 手动取消协程
}
3. 异常处理
viewModelScope.launch {
try {
val result = withContext(Dispatchers.IO) {
api.fetchData() // 可能抛出异常的操作
}
updateUI(result)
} catch (e: IOException) {
showError(e.message)
}
}
四、Flow(数据流)
1. 冷流(Cold Flow)
-
特点:只有订阅时才执行,类似 RxJava 的 Observable。
-
示例 :
// 从数据库获取实时数据 fun getUserFlow(): Flow<User> = flow { while (true) { emit(database.getUser()) // 发送数据 delay(1000) // 每秒更新一次 } }.flowOn(Dispatchers.IO) // 指定流的执行线程 // 在 ViewModel 中收集 viewModelScope.launch { userFlow.collect { user -> _liveData.value = user // 更新 LiveData } }
2. StateFlow & SharedFlow
-
StateFlow:保存最新值的热流,适合表示状态。
// ViewModel 中 private val _uiState = MutableStateFlow<UiState>(Loading) val uiState: StateFlow<UiState> = _uiState // 协程中更新状态 _uiState.value = Success(data)
-
SharedFlow:更灵活的热流,可配置重放次数、缓冲区大小。
private val _event = MutableSharedFlow<Event>() val event: SharedFlow<Event> = _event // 发送事件 viewModelScope.launch { _event.emit(ToastEvent("操作成功")) }
五、Channel(通道)
1. 基本用法
-
作用:在协程间传递数据,类似生产者 - 消费者模式。
-
示例 :
val channel = Channel<String>() // 生产者协程 viewModelScope.launch { repeat(5) { delay(1000) channel.send("消息 $it") } channel.close() // 发送完毕后关闭 } // 消费者协程 viewModelScope.launch { for (msg in channel) { Log.d("Channel", "收到: $msg") } }
六、组合多个异步操作
1. 串行执行
viewModelScope.launch {
val user = withContext(Dispatchers.IO) { api.getUser() }
val orders = withContext(Dispatchers.IO) { api.getOrders(user.id) }
updateUI(orders)
}
2. 并行执行
viewModelScope.launch {
val userDeferred = async(Dispatchers.IO) { api.getUser() }
val ordersDeferred = async(Dispatchers.IO) { api.getOrders(userId) }
val user = userDeferred.await()
val orders = ordersDeferred.await()
updateUI(user, orders)
}
3. 使用 zip
合并流
val flow1 = flow { emit(1) }
val flow2 = flow { emit("A") }
// 合并两个流的结果
flow1.zip(flow2) { num, str -> "$num-$str" }
.collect { result -> Log.d("Zip", result) } // 输出 "1-A"
七、最佳实践
-
避免在主线程执行耗时操作:
// 错误:在主线程执行网络请求 viewModelScope.launch { val data = api.fetchData() // 耗时操作,应切换到 IO 线程 } // 正确:使用 withContext 切换线程 viewModelScope.launch { val data = withContext(Dispatchers.IO) { api.fetchData() } }
-
使用 ViewModelScope 管理生命周期:
class MyViewModel : ViewModel() { fun loadData() { // 使用 viewModelScope,自动与 ViewModel 生命周期绑定 viewModelScope.launch { ... } } }
-
避免使用 GlobalScope:
// 错误:可能导致内存泄漏 GlobalScope.launch { ... } // 正确:使用限定作用域的协程 viewModelScope.launch { ... } lifecycleScope.launch { ... }
八、与其他组件结合
1. 与 LiveData 结合
// 将 Flow 转换为 LiveData
val liveData: LiveData<User> = flow {
emit(repository.getUser())
}.flowOn(Dispatchers.IO)
.asLiveData()
2. 与 Room 结合
// DAO 方法返回 Flow
@Query("SELECT * FROM users")
fun getUsers(): Flow<List<User>>
// ViewModel 中收集数据
viewModelScope.launch {
userDao.getUsers().collect { users ->
_usersLiveData.value = users
}
}
通过合理使用上述方法,你可以在 Android 中编写简洁、高效且安全的异步代码,避免回调地狱和内存泄漏问题。