一、为什么需要协程?
在Android开发中,异步操作是家常便饭。比如网络请求、数据库读写、文件操作等,如果直接在主线程执行这些耗时任务,轻则卡顿,重则ANR(应用无响应)。传统的解决方案是多线程或者回调,但它们各有缺点:
- 多线程:线程创建和切换开销大,容易引发并发问题(比如竞态条件)。
- 回调:嵌套多了会形成“回调地狱”,代码难以维护。
协程(Coroutine)是Kotlin提供的一种轻量级线程管理方案。它本质上是一个线程框架,但用起来像写同步代码一样简单。比如,你可以这样写网络请求:
// 技术栈:Kotlin + Retrofit
suspend fun fetchUserData(): User {
return withContext(Dispatchers.IO) { // 切换到IO线程
retrofit.create(UserService::class.java).getUser() // 网络请求
}
}
注释说明:
suspend:标记这是一个挂起函数,可以在协程中调用。withContext(Dispatchers.IO):指定协程在IO线程池执行。- 代码看起来是同步的,但实际是异步执行,不会阻塞主线程。
二、协程的核心概念
1. 挂起函数(Suspend Function)
挂起函数是协程的基础,用suspend修饰。它最大的特点是“挂起不阻塞”——执行到耗时操作时,协程会挂起,让出线程资源;操作完成后,再从挂起点恢复执行。
// 模拟一个耗时操作
suspend fun doHeavyWork(): String {
delay(1000) // 模拟耗时1秒
return "Work Done"
}
2. 协程作用域(CoroutineScope)
协程需要在一个作用域内启动,常见的有:
GlobalScope:全局作用域,慎用(可能内存泄漏)。lifecycleScope:与Activity/Fragment生命周期绑定。viewModelScope:在ViewModel中使用,推荐。
// 在ViewModel中启动协程
viewModelScope.launch {
val result = doHeavyWork()
Log.d("CoroutineDemo", result) // 输出:Work Done
}
3. 调度器(Dispatcher)
决定协程在哪个线程执行:
Dispatchers.Main:主线程,更新UI。Dispatchers.IO:网络/磁盘IO。Dispatchers.Default:CPU密集型计算。
三、实战:用协程处理复杂异步流
假设我们需要先请求用户数据,再根据结果请求订单列表,最后合并显示。用协程可以这样写:
// 技术栈:Kotlin + Retrofit + Room
suspend fun loadUserAndOrders(): Pair<User, List<Order>> {
val user = fetchUserData() // 挂起点1:请求用户数据
val orders = fetchOrders(user.id) // 挂起点2:请求订单
return Pair(user, orders)
}
// 在ViewModel中调用
viewModelScope.launch {
val (user, orders) = loadUserAndOrders()
updateUI(user, orders)
}
对比回调地狱版本:
fetchUserData { user ->
fetchOrders(user.id) { orders ->
updateUI(user, orders)
}
}
协程的线性逻辑明显更清晰!
四、避坑指南
1. 避免全局作用域
错误示例:
GlobalScope.launch { // 可能泄漏!
doHeavyWork()
}
正确做法:使用lifecycleScope或viewModelScope。
2. 正确处理异常
协程默认会取消父协程,但有时需要单独处理:
viewModelScope.launch {
try {
fetchData()
} catch (e: Exception) {
showError(e.message)
}
}
3. 注意取消协程
当Activity销毁时,viewModelScope会自动取消协程。但如果协程中有不可取消的代码(比如文件写入),需要用isActive检查:
viewModelScope.launch {
while (isActive) { // 检查协程是否活跃
writeToFile()
}
}
五、总结
协程的优势:
- 简洁:用同步写法写异步代码。
- 高效:轻量级线程切换,性能开销小。
- 安全:与生命周期绑定,避免内存泄漏。
适用场景:
- 网络请求、数据库操作、复杂异步流程编排。
注意事项:
- 避免滥用
GlobalScope。 - 合理选择调度器。
- 处理好异常和取消逻辑。
如果你还在用回调或RxJava,不妨试试协程,它会让你爱上异步编程!
评论