Kotlin 协程(Coroutines)详解

目录

  1. 什么是协程?
  2. 为什么需要协程?
  3. 协程基础
  4. 协程作用域
  5. 协程上下文
  6. 挂起函数
  7. Flow(数据流)
  8. [与 Java 对比](#与 Java 对比)
  9. [在 Android/Compose 中的应用](#在 Android/Compose 中的应用)
  10. 常见错误和最佳实践

1. 什么是协程?

1.1 定义

协程(Coroutine) 是 Kotlin 提供的轻量级线程,用于处理异步操作。

1.2 核心特点

  • 轻量级:可以创建成千上万个协程,开销很小
  • 挂起和恢复:可以暂停执行,稍后恢复
  • 不阻塞线程:挂起时释放线程,不阻塞
  • 结构化并发:自动管理生命周期

1.3 简单理解

协程 = 可以暂停和恢复的函数

想象一下:

  • 普通函数:执行完就结束
  • 协程:可以中途暂停,稍后继续执行

2. 为什么需要协程?

2.1 Java/Android 传统方式的问题

方式1:Thread(线程)
java 复制代码
new Thread(() -> {
    String data = fetchDataFromNetwork();
    runOnUiThread(() -> {
        textView.setText(data);
    });
}).start();

问题:

  • 创建线程开销大
  • 难以管理生命周期
  • 容易内存泄漏
  • 代码嵌套深
方式2:Handler
java 复制代码
Handler handler = new Handler(Looper.getMainLooper());
new Thread(() -> {
    String data = fetchDataFromNetwork();
    handler.post(() -> {
        textView.setText(data);
    });
}).start();

问题:

  • 代码复杂
  • 难以取消
  • 容易忘记清理
方式3:AsyncTask(已废弃)
java 复制代码
new AsyncTask<Void, Void, String>() {
    @Override
    protected String doInBackground(Void... voids) {
        return fetchDataFromNetwork();
    }
    
    @Override
    protected void onPostExecute(String data) {
        textView.setText(data);
    }
}.execute();

问题:

  • 已废弃
  • 难以测试
  • 生命周期管理复杂

2.2 协程的优势

kotlin 复制代码
// 协程写法:简洁、清晰、安全
lifecycleScope.launch {
    val data = fetchDataFromNetwork()  // 挂起,不阻塞线程
    textView.setText(data)              // 自动回到主线程
}

优势:

  • ✅ 代码简洁,类似同步代码
  • ✅ 自动管理生命周期
  • ✅ 可以轻松取消
  • ✅ 不阻塞线程
  • ✅ 结构化并发

3. 协程基础

3.1 启动协程

方式1:launch(启动一个协程)
kotlin 复制代码
// 在协程作用域中启动
lifecycleScope.launch {
    println("协程执行")
    delay(1000)  // 挂起 1 秒
    println("协程完成")
}
方式2:async(启动一个带返回值的协程)
kotlin 复制代码
val deferred = lifecycleScope.async {
    delay(1000)
    "结果"
}

val result = deferred.await()  // 等待结果
println(result)

3.2 挂起函数(suspend)

挂起函数是可以在协程中暂停执行的函数。

kotlin 复制代码
// 定义挂起函数
suspend fun fetchData(): String {
    delay(1000)  // 挂起,不阻塞线程
    return "数据"
}

// 在协程中调用
lifecycleScope.launch {
    val data = fetchData()  // 挂起,等待结果
    println(data)
}

关键点:

  • suspend 关键字标记挂起函数
  • 只能在协程或其他挂起函数中调用
  • 挂起时不阻塞线程,线程可以执行其他任务

3.3 delay vs Thread.sleep

kotlin 复制代码
// ❌ Thread.sleep:阻塞线程
Thread.sleep(1000)  // 线程被阻塞,不能做其他事

// ✅ delay:挂起协程,不阻塞线程
delay(1000)  // 协程挂起,线程可以执行其他任务

区别:

  • Thread.sleep:阻塞线程,线程不能做其他事
  • delay:挂起协程,线程可以执行其他任务

4. 协程作用域

4.1 什么是作用域?

作用域(Scope) 定义了协程的生命周期和取消规则。

4.2 常见作用域

GlobalScope(全局作用域)
kotlin 复制代码
// ❌ 不推荐:生命周期与 Application 相同,容易内存泄漏
GlobalScope.launch {
    // 协程代码
}
lifecycleScope(生命周期作用域)
kotlin 复制代码
// ✅ 推荐:与 Activity/Fragment 生命周期绑定
lifecycleScope.launch {
    // 协程代码
    // Activity 销毁时自动取消
}
viewModelScope(ViewModel 作用域)
kotlin 复制代码
// ✅ 推荐:与 ViewModel 生命周期绑定
viewModelScope.launch {
    // 协程代码
    // ViewModel 清除时自动取消
}
rememberCoroutineScope(Compose 作用域)
kotlin 复制代码
// ✅ 推荐:在 Compose 中使用
val scope = rememberCoroutineScope()
scope.launch {
    // 协程代码
}

4.3 结构化并发

结构化并发:协程的作用域是结构化的,子协程的生命周期由父协程管理。

kotlin 复制代码
lifecycleScope.launch {
    launch {  // 子协程1
        delay(1000)
        println("子协程1完成")
    }
    
    launch {  // 子协程2
        delay(2000)
        println("子协程2完成")
    }
    
    // 父协程等待所有子协程完成
}

特点:

  • 父协程取消时,所有子协程自动取消
  • 父协程等待所有子协程完成
  • 自动管理生命周期

5. 协程上下文

5.1 什么是上下文?

上下文(Context) 定义了协程的执行环境,包括:

  • Dispatcher(调度器):决定在哪个线程执行
  • Job:协程的任务
  • CoroutineName:协程名称

5.2 Dispatcher(调度器)

Dispatchers.Main(主线程)
kotlin 复制代码
lifecycleScope.launch(Dispatchers.Main) {
    // 在主线程执行(UI 线程)
    textView.setText("Hello")
}
Dispatchers.IO(IO 线程)
kotlin 复制代码
lifecycleScope.launch(Dispatchers.IO) {
    // 在 IO 线程执行(网络请求、文件操作)
    val data = fetchDataFromNetwork()
}
Dispatchers.Default(默认线程池)
kotlin 复制代码
lifecycleScope.launch(Dispatchers.Default) {
    // 在默认线程池执行(CPU 密集型任务)
    val result = heavyComputation()
}
Dispatchers.Unconfined(不限制)
kotlin 复制代码
// ⚠️ 不推荐:不限制线程,可能在不同线程执行
lifecycleScope.launch(Dispatchers.Unconfined) {
    // 可能在不同线程执行
}

5.3 切换线程

kotlin 复制代码
lifecycleScope.launch(Dispatchers.Main) {
    // 在主线程
    textView.setText("开始")
    
    withContext(Dispatchers.IO) {
        // 切换到 IO 线程
        val data = fetchDataFromNetwork()
    }
    
    // 自动切回主线程
    textView.setText("完成")
}

关键点:

  • withContext 切换线程上下文
  • 执行完后自动切回原来的线程
  • 挂起函数,不阻塞线程

6. 挂起函数

6.1 定义挂起函数

kotlin 复制代码
suspend fun fetchUser(id: String): User {
    delay(1000)  // 模拟网络请求
    return User(id, "张三")
}

suspend fun fetchPosts(userId: String): List<Post> {
    delay(1000)  // 模拟网络请求
    return listOf(Post("文章1"), Post("文章2"))
}

6.2 调用挂起函数

kotlin 复制代码
lifecycleScope.launch {
    val user = fetchUser("123")
    val posts = fetchPosts(user.id)
    // 使用数据
}

6.3 并发执行

kotlin 复制代码
lifecycleScope.launch {
    // 并发执行两个请求
    val deferredUser = async { fetchUser("123") }
    val deferredPosts = async { fetchPosts("123") }
    
    // 等待两个请求完成
    val user = deferredUser.await()
    val posts = deferredPosts.await()
    
    // 使用数据
}

6.4 异常处理

kotlin 复制代码
lifecycleScope.launch {
    try {
        val data = fetchData()
        // 使用数据
    } catch (e: Exception) {
        // 处理异常
        println("错误: ${e.message}")
    }
}

7. Flow(数据流)

7.1 什么是 Flow?

Flow 是 Kotlin 协程提供的异步数据流,类似于 RxJava 的 Observable。

7.2 创建 Flow

kotlin 复制代码
fun numbers(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(100)
        emit(i)  // 发射数据
    }
}

7.3 收集 Flow

kotlin 复制代码
lifecycleScope.launch {
    numbers().collect { number ->
        println(number)
    }
}

7.4 Flow 操作符

kotlin 复制代码
lifecycleScope.launch {
    numbers()
        .filter { it % 2 == 0 }      // 过滤
        .map { it * 2 }               // 转换
        .collect { value ->
            println(value)
        }
}

7.5 在 Compose 中使用 Flow

kotlin 复制代码
@Composable
fun FlowExample() {
    val viewModel: MyViewModel = viewModel()
    val data by viewModel.dataFlow.collectAsState()
    
    Text(data)
}

8. 与 Java 对比

8.1 网络请求

Java 传统方式
java 复制代码
new Thread(() -> {
    try {
        String data = fetchDataFromNetwork();
        runOnUiThread(() -> {
            textView.setText(data);
        });
    } catch (Exception e) {
        runOnUiThread(() -> {
            // 处理错误
        });
    }
}).start();
Kotlin 协程
kotlin 复制代码
lifecycleScope.launch {
    try {
        val data = fetchDataFromNetwork()
        textView.setText(data)
    } catch (e: Exception) {
        // 处理错误
    }
}

8.2 多个异步操作

Java 传统方式
java 复制代码
CountDownLatch latch = new CountDownLatch(2);
String[] results = new String[2];

new Thread(() -> {
    results[0] = fetchUser();
    latch.countDown();
}).start();

new Thread(() -> {
    results[1] = fetchPosts();
    latch.countDown();
}).start();

latch.await();
// 使用 results
Kotlin 协程
kotlin 复制代码
lifecycleScope.launch {
    val deferredUser = async { fetchUser() }
    val deferredPosts = async { fetchPosts() }
    
    val user = deferredUser.await()
    val posts = deferredPosts.await()
    
    // 使用数据
}

8.3 定时任务

Java 传统方式
java 复制代码
Handler handler = new Handler(Looper.getMainLooper());
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        // 执行任务
        handler.postDelayed(this, 1000);
    }
};
handler.post(runnable);

// 取消
handler.removeCallbacks(runnable);
Kotlin 协程
kotlin 复制代码
val job = lifecycleScope.launch {
    while (isActive) {
        // 执行任务
        delay(1000)
    }
}

// 取消
job.cancel()

9. 在 Android/Compose 中的应用

9.1 在 Activity/Fragment 中使用

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        lifecycleScope.launch {
            val data = fetchData()
            // 更新 UI
        }
    }
}

9.2 在 ViewModel 中使用

kotlin 复制代码
class MyViewModel : ViewModel() {
    private val _data = MutableStateFlow<String?>(null)
    val data: StateFlow<String?> = _data
    
    fun loadData() {
        viewModelScope.launch {
            val result = fetchData()
            _data.value = result
        }
    }
}

9.3 在 Compose 中使用

方式1:LaunchedEffect
kotlin 复制代码
@Composable
fun MyScreen() {
    var data by remember { mutableStateOf<String?>(null) }
    
    LaunchedEffect(Unit) {
        data = fetchData()
    }
    
    data?.let { Text(it) }
}
方式2:rememberCoroutineScope
kotlin 复制代码
@Composable
fun MyScreen() {
    val scope = rememberCoroutineScope()
    var data by remember { mutableStateOf<String?>(null) }
    
    Button(onClick = {
        scope.launch {
            data = fetchData()
        }
    }) {
        Text("加载")
    }
}
方式3:collectAsState
kotlin 复制代码
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
    val data by viewModel.dataFlow.collectAsState()
    
    data?.let { Text(it) }
}

10. 常见错误和最佳实践

10.1 ❌ 错误1:在非协程作用域中调用挂起函数

kotlin 复制代码
// ❌ 错误:不能在普通函数中直接调用挂起函数
fun loadData() {
    val data = fetchData()  // 编译错误!
}

✅ 正确:

kotlin 复制代码
fun loadData() {
    lifecycleScope.launch {
        val data = fetchData()  // 在协程中调用
    }
}

10.2 ❌ 错误2:使用 GlobalScope

kotlin 复制代码
// ❌ 错误:容易内存泄漏
GlobalScope.launch {
    fetchData()
}

✅ 正确:

kotlin 复制代码
lifecycleScope.launch {
    fetchData()
}

10.3 ❌ 错误3:忘记处理异常

kotlin 复制代码
// ❌ 错误:异常可能导致崩溃
lifecycleScope.launch {
    val data = fetchData()  // 可能抛出异常
}

✅ 正确:

kotlin 复制代码
lifecycleScope.launch {
    try {
        val data = fetchData()
    } catch (e: Exception) {
        // 处理异常
    }
}

10.4 ✅ 最佳实践1:使用正确的 Dispatcher

kotlin 复制代码
// ✅ 网络请求用 IO
lifecycleScope.launch(Dispatchers.IO) {
    val data = fetchDataFromNetwork()
    withContext(Dispatchers.Main) {
        // 更新 UI
    }
}

// ✅ CPU 密集型任务用 Default
lifecycleScope.launch(Dispatchers.Default) {
    val result = heavyComputation()
}

10.5 ✅ 最佳实践2:使用 viewModelScope

kotlin 复制代码
class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {  // ✅ 使用 viewModelScope
            val data = fetchData()
        }
    }
}

10.6 ✅ 最佳实践3:取消不需要的协程

kotlin 复制代码
val job = lifecycleScope.launch {
    // 长时间运行的任务
}

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

10.7 ✅ 最佳实践4:使用 Flow 处理数据流

kotlin 复制代码
class MyViewModel : ViewModel() {
    private val _data = MutableStateFlow<String?>(null)
    val data: StateFlow<String?> = _data
    
    fun observeData() {
        viewModelScope.launch {
            dataFlow.collect { value ->
                _data.value = value
            }
        }
    }
}

总结

协程核心概念速查表

概念 说明 示例
协程 轻量级线程 launch { }
挂起函数 可以暂停的函数 suspend fun fetchData()
作用域 协程的生命周期 lifecycleScope, viewModelScope
Dispatcher 线程调度器 Dispatchers.Main, Dispatchers.IO
Flow 异步数据流 flow { emit(1) }

核心原则

  1. 使用正确的作用域lifecycleScopeviewModelScope
  2. 使用正确的 Dispatcher:Main(UI)、IO(网络)、Default(计算)
  3. 处理异常:使用 try-catch
  4. 避免 GlobalScope:容易内存泄漏
  5. 使用 Flow:处理数据流

记忆口诀

  • launch = "启动协程,不关心返回值"
  • async = "启动协程,需要返回值"
  • suspend = "挂起函数,可以暂停"
  • withContext = "切换线程上下文"
  • Flow = "异步数据流"

与 Java 对比

Java Kotlin 协程
Thread launch
Handler.post() Dispatchers.Main
AsyncTask viewModelScope.launch
RxJava Flow

掌握这些,你就能在 Kotlin/Compose 中高效处理异步操作!

相关推荐
阿里嘎多学长2 小时前
2025-12-29 GitHub 热点项目精选
开发语言·程序员·github·代码托管
鹿角片ljp2 小时前
深入理解Java集合框架:核心接口与实现解析
java·开发语言·windows
小灰灰搞电子2 小时前
C++ 文件操作详解
开发语言·c++·文件操作
让学习成为一种生活方式2 小时前
如何根据过滤的pep序列进一步过滤gff3文件--python015
开发语言·人工智能·python
heartbeat..2 小时前
Java NIO 详解(Channel+Buffer+Selector)
java·开发语言·文件·nio
云栖梦泽2 小时前
易语言开发者的知识沉淀与生态传承:从“用会”到“传好”
开发语言
allk552 小时前
Android APK 极限瘦身:从构建链优化到架构演进
android·架构
2401_837088502 小时前
Hot 146 LRU Cache 实现详解
java·开发语言
啊西:2 小时前
SuperMap iMobile for Android中模型按照指定路径运动
android