1. 阻塞主线程
- 错误: 在
Dispatchers.Main
中使用Thread.sleep()
或阻塞式调用。 - 问题: 冻结 UI,导致 ANR(应用程序无响应)。
- 修复: 在协程中,使用
delay()
代替Thread.sleep()
来实现非阻塞式延迟。
kotlin
// 错误
Thread.sleep(1000)
// 正确
delay(1000)
2. 不加区分
地使用 GlobalScope
- 错误: 在
GlobalScope
中启动协程,而没有生命周期感知。 - 问题: 这会导致内存泄漏和不必要的资源消耗。
- 修复: 使用
lifecycleScope
或viewModelScope
来启动作用域协程。
kotlin
// 错误
GlobalScope.launch {
fetchData()
}
// 正确
viewModelScope.launch {
fetchData()
}
3. 未正确处理异常
- 错误: 忽略协程异常或未正确使用
try-catch
。 - 问题: 导致协程崩溃和未处理的异常。
- 修复: 使用
CoroutineExceptionHandler
或try-catch
进行结构化错误处理。
kotlin
val handler = CoroutineExceptionHandler { _, exception ->
Log.e("Coroutine", "Caught exception: $exception")
}
CoroutineScope(Dispatchers.IO + handler).launch {
try {
riskyOperation()
} catch (e: Exception) {
Log.e("Coroutine", "Handled exception: $e")
}
}
4. 忽略结构化并发
- 错误: 启动多个协程而不管理它们的生命周期。
- 问题: 协程无限期运行,导致内存泄漏或意外行为。
- 修复: 使用
coroutineScope
或supervisorScope
进行结构化并发。
kotlin
// 错误
launch { fetchData1() }
launch { fetchData2() }
// 正确
coroutineScope {
launch { fetchData1() }
launch { fetchData2() }
}
5. 滥用 Dispatchers.IO
和 Dispatchers.Default
- 错误: 在
Dispatchers.IO
上执行 CPU 密集型任务,反之亦然。 - 问题: 导致性能不佳和线程饥饿。
- 修复: 将
Dispatchers.Default
用于计算任务,将Dispatchers.IO
用于 I/O 操作。
kotlin
// CPU密集任务
withContext(Dispatchers.Default) {
processLargeData()
}
// I/O 操作
withContext(Dispatchers.IO) {
fetchFromNetwork()
}
6. 过度使用 withContext
- 错误: 不必要地嵌套多个
withContext
代码块。 - 问题: 导致线程切换开销并降低可读性。
- 修复: 将相关操作归入单个上下文。
scss
// 错误
withContext(Dispatchers.IO) { operation1() }
withContext(Dispatchers.IO) { operation2() }
// 正确
withContext(Dispatchers.IO) {
operation1()
operation2()
}
7. 忘记取消协程
- 错误: 当不再需要时,不取消与生命周期绑定的协程。
- 问题: 浪费资源,如果协程与已销毁的视图交互,可能导致崩溃。
- 修复: 明确取消协程或使用生命周期感知的作用域。
kotlin
override fun onDestroy() {
super.onDestroy()
coroutineScope.cancel() // 明确取消
}
8. 在自定义作用域中忽略协程上下文
- 错误: 创建自定义
CoroutineScope
时未指定正确的上下文。 - 问题: 导致意外行为或协程泄漏。
- 修复: 创建自定义作用域时,始终提供一个上下文。
kotlin
val customScope = CoroutineScope(Dispatchers.Main + Job())
customScope.launch {
// 协程代码
}
9. 错误使用 async
启动任务
- 错误: 使用
async
而不加await()
,或错误地将launch
用于需要结果的任务。 - 问题: 这可能导致结果丢失或不必要的后台任务。
- 修复: 当你需要结果时使用
async
,当执行"即发即弃"(fire-and-forget)操作时使用launch
。
kotlin
// 正确用法
val result = async { fetchData() }
Log.d("Result", result.await())
10. 未充分利用 Flow 处理数据流
- 错误: 使用普通的协程来处理连续或响应式的数据流。
- 问题: 管理数据流效率低下,缺乏背压(backpressure)或生命周期处理。
- 修复: 使用 Kotlin
Flow
或StateFlow
进行响应式数据流和状态管理。
kotlin
val dataFlow = flow {
emit(fetchData())
}.flowOn(Dispatchers.IO)
lifecycleScope.launch {
dataFlow.collect { data ->
updateUI(data)
}
}
11. 误解 launch
与 runBlocking
- 错误: 在生产代码中使用
runBlocking
或将其与launch
混淆。 - 问题:
runBlocking
会阻塞线程,不适用于非测试环境。 - 修复: 将
launch
用于异步任务,runBlocking
仅用于测试(单元测试推荐使用runTest
)。
scss
// 错误 (生产环境)
runBlocking {
performTask()
}
// 正确
launch {
performTask()
}
通过避免这些常见的陷阱,高级 Android 开发者可以确保他们的 Kotlin 协程实现是高效、健壮且易于维护的。 欢迎关注我的公众号:OpenFlutter