Kotlin协程最佳实践:Android开发者必知的5个坑
Kotlin协程已经成为现代Android应用开发中处理异步任务的标准工具。它通过挂起而非阻塞的方式,让异步代码写起来像同步代码一样直观。然而,强大的工具往往伴随着误用的风险。很多开发者在刚上手协程时,会不自觉地陷入一些常见的陷阱,导致应用出现内存泄漏、性能下降,甚至直接崩溃。
本文不讨论协程的基础语法,而是深入总结Android开发者在真实业务中最容易踩到的5个坑,并提供经过验证的最佳实践方案。无论你是初学者还是有一定经验的开发者,这篇文章都能帮你避开这些隐藏的“雷区”。

坑1:使用GlobalScope创建无法取消的长效任务
问题描述:有些开发者喜欢用GlobalScope.launch来启动协程,因为这样无需手动管理作用域,在任何地方都能调用。但这种做法极度危险,因为GlobalScope的生命周期与应用进程一致,它不受任何Android组件(如Activity或Fragment)的生命周期管控。
危害:如果你在Activity中发起了一个网络请求,且使用了GlobalScope,当用户退出页面时,协程仍在后台运行。如果此时协程回调中涉及更新UI,会直接触发IllegalStateException,甚至导致内存泄漏。
最佳实践方案
务必使用与Android组件生命周期绑定的作用域:
- UI层使用
lifecycleScope(ViewModel中则使用viewModelScope)。 - 当组件销毁时,
lifecycleScope会自动取消所有子协程,安全可靠。 - 避免在Activity或Fragment中直接通过
CoroutineScope(Dispatchers.Main)自行创建作用域,除非你能在onDestroy()中手动调用cancel()。
坑2:在主线程进行高耗时操作导致卡顿
问题描述:协程的便利性让一些人误以为只要在协程里,代码就不会阻塞主线程。实际上,Dispatchers.Main上的协程默认工作在UI线程。如果你在launch中直接进行数据库查询或解析大型JSON,这些工作会直接卡住UI。
危害:用户会明显感觉到界面掉帧、点击无响应,严重时系统会弹出“应用无响应”(ANR)对话框。
最佳实践方案
根据任务类型显式指定调度器:
- 网络请求和JSON解析使用
Dispatchers.IO。 - CPU密集型计算(如位图处理、列表排序)使用
Dispatchers.Default。 - 使用
withContext在挂起点切换线程,底层自带线程池优化,效率极高。
示例: withContext(Dispatchers.IO) { fetchUserData() },将耗时操作放在IO线程池,执行完毕后自动切回主线程更新UI。
坑3:协程中被忽略的取消响应机制
问题描述:很多开发者认为协程被取消后,内部的代码会立即停止执行。事实并非如此。协程的取消是协作式的,即代码必须配合检查取消状态才能及时终止。
危害:如果你在协程内执行一个不支持取消的耗时循环或阻塞代码(如Thread.sleep()),即使外层协程被取消,内部代码仍会执行到最后,造成资源浪费。
最佳实践方案
确保耗时操作“可取消”:
- 使用
kotlinx.coroutines中的挂起函数(如delay()、withContext())替代Java原生阻塞操作。 - 对于自定义的循环逻辑,定期检查
coroutineContext.isActive或调用ensureActive()方法。 - 当使用第三方阻塞库时,将其包装在
withContext(Dispatchers.IO)中,并在外部捕获CancellationException处理清理逻辑。
坑4:未限制并发导致大量任务堆积
问题描述:在列表加载图片或发起批量请求时,有些开发者会直接使用 async 或 launch 发起几十个并发任务,而不加任何限制。虽然协程比线程轻量,但无限制的并发仍然会耗尽内存和CPU资源。
危害:应用内存激增,甚至出现 OutOfMemoryError(OOM);同时过多的线程切换会降低整体执行效率。
最佳实践方案
引入并发控制机制:
- 使用
Semaphore信号量控制同时运行的协程数量。 - 或者在IO调度器中指定自定义线程池大小,例如
newFixedThreadPoolContext(4, "IO-bound")。 - 在Android中最优雅的方式是使用
Flow配合flatMapMerge(concurrency = 4)来限制并发数,代码更简洁易读。
坑5:异常处理不当引发应用崩溃
问题描述:协程中的异常处理与普通Java回调完全不同。默认情况下,在 launch 中抛出的异常会直接传递给线程的UncaughtExceptionHandler,从而导致App崩溃。
危害:例如在viewModelScope.launch中调用API,一旦返回错误或发生网络异常,而未捕获异常,整个应用会直接闪退,用户体验极差。
最佳实践方案
遵循以下异常处理原则:
- 在
launch块内使用try-catch捕获业务异常。 - 在根协程(或顶层协程)中使用
CoroutineExceptionHandler作为最后防线。 - 对于需要并行执行的任务,使用
SupervisorJob隔离异常,确保子协程之间互不影响。 - 在ViewModel中,通常将异常转化为
LiveData或StateFlow中的UI状态,而非直接抛出。
总结:Kotlin协程虽好,但需要用正确的方式打开。记住这5个关键点——不随意使用GlobalScope、根据场景切换调度器、保持协程的可取消性、合理限制并发数、妥善处理异常并隔离故障。当你避开这些坑,协程将是提升Android开发效率的利器,让你的应用更稳定、更流畅。