还在为异步编程头疼?还在用 Callback 地狱?今天就来聊聊 Kotlin 协程,让你的异步代码变得优雅起来!

作为一个 Android 开发者,你一定遇到过这些场景:

- 网络请求需要等待响应

- 数据库操作不能阻塞主线程

- 多个异步任务需要协调执行

- 处理回调嵌套让人抓狂传统的解决方案(线程、Handler、RxJava)要么太复杂,要么容易出错。而 **Kotlin 协程**就是为解决这些问题而生的!

想象一下,你在做一道菜:

1. 切菜(暂停)

2. 等水开(暂停)

3. 炒菜(暂停)

4. 装盘(完成)

协程就像这样,可以在某个地方暂停,等条件满足后再继续,而且**不会阻塞线程**!

协程的三大优势

✅ **轻量级**:一个线程可以运行成千上万个协程

✅ **简单**:代码看起来像同步代码,但实际是异步执行

✅ **安全**:自动管理生命周期,避免内存泄漏

第一步:添加依赖

在你的 `build.gradle.kts` 中添加:

dependencies {
    // 协程核心库
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
    
    // Android 主线程支持
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
    
    // ViewModel 协程支持(强烈推荐)
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
    
    // Activity/Fragment 协程支持
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
}

同步一下项目,就可以开始使用了!

核心概念:理解这 4 个就够了      

1️⃣ 协程作用域(CoroutineScope)

作用域决定了协程的生命周期。就像给协程一个"家",家没了,协程也就结束了。

// ❌ 不推荐:GlobalScope(协程会一直运行,容易内存泄漏)
GlobalScope.launch {
    // 危险!
}

// ✅ 推荐:lifecycleScope(Activity/Fragment 中使用)
lifecycleScope.launch {
    // Activity 销毁时自动取消,安全!
}

// ✅ 推荐:viewModelScope(ViewModel 中使用)
viewModelScope.launch {
    // ViewModel 清除时自动取消,最安全!
}

记住:99% 的情况下,用 viewModelScope 或 lifecycleScope 就够了!  

2️⃣ 调度器(Dispatchers)

调度器决定协程在哪个线程执行。就像给协程分配工作地点

// 主线程 - 更新 UI
launch(Dispatchers.Main) {
    textView.text = "Hello"
}

// IO 线程 - 网络请求、数据库操作、文件读写
launch(Dispatchers.IO) {
    val data = api.getData()
}

// 默认线程 - CPU 密集型计算
launch(Dispatchers.Default) {
    val result = heavyComputation()
}

简单记忆:

  • UI 更新 → Dispatchers.Main

  • 网络/数据库 → Dispatchers.IO

  • 复杂计算 → Dispatchers.Default

3️⃣ 挂起函数(Suspend Function)

挂起函数是可以暂停的函数,用 suspend 关键字标记

// 定义挂起函数
suspend fun fetchUserData(): User {
    return withContext(Dispatchers.IO) {
        delay(1000) // 模拟网络延迟
        User("John", 25)
    }
}

// 调用挂起函数(必须在协程中)
viewModelScope.launch {
    val user = fetchUserData() // 看起来像同步代码,实际是异步的!
    println(user.name)
}

4️⃣ 协程构建器

构建器用来启动协程,主要有 3 种

// 1. launch - 启动一个"发后不理"的协程
launch {
    doSomething()
}

// 2. async - 启动一个需要返回值的协程
val deferred = async {
    delay(1000)
    "结果"
}
val result = deferred.await() // 等待结果

// 3. runBlocking - 阻塞线程(仅用于测试)
runBlocking {
    delay(1000)
}

实战:5 个最常见的场景

场景 1:在 Activity 中加载数据

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        lifecycleScope.launch {
            // 显示加载提示
            progressBar.visibility = View.VISIBLE
            
            // 在后台线程获取数据
            val data = withContext(Dispatchers.IO) {
                repository.fetchData()
            }
            
            // 自动切回主线程更新 UI
            textView.text = data
            progressBar.visibility = View.GONE
        }
    }
}

要点:

  • 使用 lifecycleScope,Activity 销毁时自动取消
  • 用 withContext(Dispatchers.IO) 切换到后台线程
  • 协程会自动切回主线程,可以直接更新 UI

场景 2:在 ViewModel 中处理业务逻辑

class UserViewModel : ViewModel() {
    private val repository = UserRepository()
    
    val users = MutableLiveData<List<User>>()
    val error = MutableLiveData<String>()
    
    fun loadUsers() {
        viewModelScope.launch {
            try {
                val userList = repository.getUsers()
                users.value = userList
            } catch (e: Exception) {
                error.value = "加载失败: ${e.message}"
            }
        }
    }
}

要点:

  • 使用 viewModelScope,最安全的选择
  • 在 ViewModel 中处理业务逻辑,不直接操作 UI
  • 用 try-catch 处理异常

场景 3:Room 数据库操作

@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    suspend fun getAllUsers(): List<User> // 注意 suspend 关键字
}

class UserRepository(private val userDao: UserDao) {
    suspend fun getAllUsers(): List<User> {
        return withContext(Dispatchers.IO) {
            userDao.getAllUsers() // Room 的 suspend 函数会自动切换线程
        }
    }
}

// 在 ViewModel 中使用
viewModelScope.launch {
    val users = repository.getAllUsers()
    _users.value = users
}

    要点:

  • Room 的 DAO 方法可以标记为 suspend
  • Room 会自动处理线程切换,但建议显式使用 withContext

场景 4:网络请求(Retrofit)

interface ApiService {
    @GET("users")
    suspend fun getUsers(): Response<List<User>> // Retrofit 支持 suspend
}

class ApiRepository(private val apiService: ApiService) {
    suspend fun fetchUsers(): Result<List<User>> = withContext(Dispatchers.IO) {
        try {
            val response = apiService.getUsers()
            if (response.isSuccessful) {
                Result.success(response.body() ?: emptyList())
            } else {
                Result.failure(Exception("网络错误"))
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

// 在 ViewModel 中使用
viewModelScope.launch {
    when (val result = repository.fetchUsers()) {
        is Result.Success -> _users.value = result.data
        is Result.Failure -> _error.value = result.exception.message
    }
}

要点:

  • Retrofit 2.6+ 直接支持 suspend 函数
  • 不需要 Callback,代码更简洁
  • 用 Result 类型处理成功/失败

场景 5:并行执行多个任务

viewModelScope.launch {
    // 同时启动 3 个任务
    val deferred1 = async(Dispatchers.IO) { fetchUserProfile() }
    val deferred2 = async(Dispatchers.IO) { fetchUserSettings() }
    val deferred3 = async(Dispatchers.IO) { fetchUserMessages() }
    
    // 等待所有任务完成
    val profile = deferred1.await()
    val settings = deferred2.await()
    val messages = deferred3.await()
    
    // 使用结果
    updateUI(profile, settings, messages)
}

要点:

  • 用 async 并行执行
  • 用 await() 等待结果
  • 比顺序执行快得多!

进阶技巧:让你的代码更专业

1. 超时处理

viewModelScope.launch {
    try {
        val result = withTimeout(5000) { // 5 秒超时
            fetchDataFromNetwork()
        }
        // 使用 result
    } catch (e: TimeoutCancellationException) {
        showError("操作超时,请重试")
    }
}

2. 取消协程

val job = viewModelScope.launch {
    repeat(1000) { i ->
        delay(100)
        println("Running: $i")
    }
}

// 需要时取消
job.cancel()

注意: 通常不需要手动取消,viewModelScope 会自动处理!

3. 异常处理

viewModelScope.launch {
    try {
        val data = repository.fetchData()
        _data.value = data
    } catch (e: IOException) {
        // 网络错误
        _error.value = "网络连接失败"
    } catch (e: Exception) {
        // 其他错误
        _error.value = "发生错误: ${e.message}"
    }
}

4. SupervisorJob - 独立失败

// 使用 SupervisorJob,一个协程失败不影响其他协程
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

scope.launch {
    fetchData1() // 失败不影响下面的
}

scope.launch {
    fetchData2() // 继续执行
}

常见坑和解决方案

坑 1:协程没有被取消

问题: Activity 销毁了,但协程还在运行

// ❌ 错误:使用 GlobalScope
GlobalScope.launch {
    // 协程会一直运行
}

// ✅ 正确:使用 lifecycleScope
lifecycleScope.launch {
    // Activity 销毁时自动取消
}

坑 2:内存泄漏

问题: 协程持有 Activity 引用

// ❌ 错误:在 Activity 中直接执行业务逻辑
class MainActivity : AppCompatActivity() {
    fun loadData() {
        lifecycleScope.launch {
            val data = heavyOperation() // 可能持有 Activity 引用
        }
    }
}

// ✅ 正确:在 ViewModel 中处理
class MainViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            val data = repository.fetchData() // ViewModel 不持有 View 引用
        }
    }
}

坑 3:忘记处理异常

// ❌ 错误:没有异常处理
viewModelScope.launch {
    val data = repository.fetchData() // 可能崩溃
    _data.value = data
}

// ✅ 正确:处理异常
viewModelScope.launch {
    try {
        val data = repository.fetchData()
        _data.value = data
    } catch (e: Exception) {
        _error.value = e.message
    }
}

坑 4:频繁切换线程

// ❌ 错误:频繁切换
viewModelScope.launch {
    withContext(Dispatchers.IO) { /* 操作1 */ }
    withContext(Dispatchers.Main) { /* UI更新 */ }
    withContext(Dispatchers.IO) { /* 操作2 */ }
}

// ✅ 正确:批量操作
viewModelScope.launch {
    val results = withContext(Dispatchers.IO) {
        val result1 = operation1()
        val result2 = operation2()
        Pair(result1, result2)
    }
    // 在主线程更新 UI
    updateUI(results.first, results.second)
}

最佳实践总结

✅ 推荐做法

  1. 使用合适的作用域
  • Activity/Fragment → lifecycleScope
  • ViewModel → viewModelScope
  1. 正确切换线程
  • IO 操作 → Dispatchers.IO
  • UI 更新 → Dispatchers.Main(默认)
  1. 处理异常
  • 总是用 try-catch 包裹可能失败的操作
  1. 避免阻塞
  • 用 delay() 而不是 Thread.sleep()

❌ 避免的做法

  1. 不要用 GlobalScope(除非明确需要)
  2. 不要在协程中阻塞线程(用 delay 代替 sleep)
  3. 不要忘记异常处理
  4. 不要手动管理 Job(让作用域自动处理)

快速参考卡片

基础模式

// Activity/Fragment
lifecycleScope.launch {
    val data = withContext(Dispatchers.IO) {
        repository.fetchData()
    }
    updateUI(data)
}

// ViewModel
viewModelScope.launch {
    try {
        val data = repository.fetchData()
        _data.value = data
    } catch (e: Exception) {
        _error.value = e.message
    }
}

// 并行执行
val result1 = async { fetchData1() }
val result2 = async { fetchData2() }
val combined = awaitAll(result1, result2)

写在最后

协程看起来复杂,但用起来真的很简单!记住这几点:

  1. 99% 的情况用 viewModelScope 就够了
  2. IO 操作用 withContext(Dispatchers.IO)
  3. 记得处理异常
  4. 让系统自动管理生命周期

多写多练,很快就能上手。如果遇到问题,欢迎在评论区讨论!

如果觉得有用,记得点赞收藏! 👍

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐