从零到一:Kotlin 协程完全指南
还在为异步编程头疼?还在用 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)
}
最佳实践总结
✅ 推荐做法
- 使用合适的作用域
- Activity/Fragment → lifecycleScope
- ViewModel → viewModelScope
- 正确切换线程
- IO 操作 → Dispatchers.IO
- UI 更新 → Dispatchers.Main(默认)
- 处理异常
- 总是用 try-catch 包裹可能失败的操作
- 避免阻塞
- 用 delay() 而不是 Thread.sleep()
❌ 避免的做法
- 不要用 GlobalScope(除非明确需要)
- 不要在协程中阻塞线程(用 delay 代替 sleep)
- 不要忘记异常处理
- 不要手动管理 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)
写在最后
协程看起来复杂,但用起来真的很简单!记住这几点:
- 99% 的情况用 viewModelScope 就够了
- IO 操作用 withContext(Dispatchers.IO)
- 记得处理异常
- 让系统自动管理生命周期
多写多练,很快就能上手。如果遇到问题,欢迎在评论区讨论!
如果觉得有用,记得点赞收藏! 👍
更多推荐



所有评论(0)