目录
- 什么是协程?
- 为什么需要协程?
- 协程基础
- 协程作用域
- 协程上下文
- 挂起函数
- Flow(数据流)
- [与 Java 对比](#与 Java 对比)
- [在 Android/Compose 中的应用](#在 Android/Compose 中的应用)
- 常见错误和最佳实践
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) } |
核心原则
- 使用正确的作用域 :
lifecycleScope、viewModelScope - 使用正确的 Dispatcher:Main(UI)、IO(网络)、Default(计算)
- 处理异常:使用 try-catch
- 避免 GlobalScope:容易内存泄漏
- 使用 Flow:处理数据流
记忆口诀
- launch = "启动协程,不关心返回值"
- async = "启动协程,需要返回值"
- suspend = "挂起函数,可以暂停"
- withContext = "切换线程上下文"
- Flow = "异步数据流"
与 Java 对比
| Java | Kotlin 协程 |
|---|---|
Thread |
launch |
Handler.post() |
Dispatchers.Main |
AsyncTask |
viewModelScope.launch |
RxJava |
Flow |
掌握这些,你就能在 Kotlin/Compose 中高效处理异步操作!