一、为什么需要协程?

在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()
}

正确做法:使用lifecycleScopeviewModelScope

2. 正确处理异常

协程默认会取消父协程,但有时需要单独处理:

viewModelScope.launch {
    try {
        fetchData()
    } catch (e: Exception) {
        showError(e.message)
    }
}

3. 注意取消协程

当Activity销毁时,viewModelScope会自动取消协程。但如果协程中有不可取消的代码(比如文件写入),需要用isActive检查:

viewModelScope.launch {
    while (isActive) { // 检查协程是否活跃
        writeToFile()
    }
}

五、总结

协程的优势:

  • 简洁:用同步写法写异步代码。
  • 高效:轻量级线程切换,性能开销小。
  • 安全:与生命周期绑定,避免内存泄漏。

适用场景:

  • 网络请求、数据库操作、复杂异步流程编排。

注意事项:

  • 避免滥用GlobalScope
  • 合理选择调度器。
  • 处理好异常和取消逻辑。

如果你还在用回调或RxJava,不妨试试协程,它会让你爱上异步编程!