
目录一、使用技巧与最佳实践1. 作用域选择避免内存泄漏2. 调度器 Dispatchers 正确选择3. 线程切换用 withContext并发用 async4. 异常处理5. 超时与取消6. Repository 层设计原则二、常见应用场景三、常见坑提醒一、使用技巧与最佳实践1. 作用域选择避免内存泄漏永远使用生命周期感知的作用域禁止在业务代码中使用GlobalScope其生命周期与进程一致不会自动取消。作用域绑定对象取消时机适用场景viewModelScopeViewModelonCleared()ViewModel 中发起网络/数据库请求lifecycleScopeActivity/FragmentonDestroy()UI 相关异步倒计时、动画、ToastrememberCoroutineScope()Compose 组合离开 CompositionCompose 点击事件内启动协程Kotlin 协程Coroutines是 Android 异步编程的首选方案2. 调度器 Dispatchers 正确选择Dispatchers.Main — UI 线程更新 View、LiveData/StateFlowDispatchers.IO — 网络请求Retrofit、Room 数据库、文件读写Dispatchers.Default — CPU 密集型JSON 解析、排序、加解密Dispatchers.Main.immediate — 已在主线程时避免多余消息队列投递⚠️ Room 和 Retrofit 的suspend函数内部已自动切到 IORepository 层用withContext(Dispatchers.IO)包裹即可调用方无需再切。3. 线程切换用withContext并发用async// 顺序切换最常用 viewModelScope.launch { val data withContext(Dispatchers.IO) { api.fetch() } // IO _state.value data // 自动回 Main } // 并行请求加速页面加载 viewModelScope.launch { val userDeferred async { api.getUser() } val feedDeferred async { api.getFeed() } show(userDeferred.await(), feedDeferred.await()) }launch→ 不返回结果fire-and-forgetasync→ 返回 DeferredT通过 await()取结果优先用 withContext而非 async{...}.await()做单纯线程切换4. 异常处理// 方式一try-catch推荐用于业务逻辑 viewModelScope.launch { try { _state.value withContext(Dispatchers.IO) { api.fetch() } } catch (e: IOException) { _state.value UiState.Error(e.message) } } // 方式二CoroutineExceptionHandler顶层兜底 val handler CoroutineExceptionHandler { _, e - Log.e(TAG, e.toString()) } viewModelScope.launch(handler) { /* ... */ }supervisorScope可让子协程异常互不传播适合多个独立任务并行。5. 超时与取消viewModelScope.launch { try { withTimeout(5000) { // 5秒超时自动取消 val data api.fetchSlow() _state.value data } } catch (e: TimeoutCancellationException) { _state.value UiState.Timeout } }协程取消是协作式的长时间计算循环中用ensureActive()或isActive检查取消。6. Repository 层设计原则suspend函数应主线程安全——内部自己切 Dispatchers.IOViewModel 直接调用无需关心线程suspend fun getNews(): ListNews withContext(Dispatchers.IO) { api.fetchNews().also { db.newsDao().insert(it) } }二、常见应用场景场景做法网络请求 刷新 UIviewModelScope.launch withContext(IO)Room 数据库增删改查DAO 声明suspend调用时withContext(IO)或直接 collect Flow并行接口用户信息列表async { ... } await()两个请求并发执行倒计时/轮询/动画lifecycleScope.launch { while(isActive) { delay(1000); tick() } }Flow 数据观察flow.collectAsStateWithLifecycle()在 Composeflow.onEach{}.launchIn(viewModelScope)在传统 View退出页面仍需完成的任务如日志上报、文件上传使用 Application 级自定义CoroutineScope(SupervisorJob()IO)不依赖 ViewModel防重复点击配合debounceFlow或在点击时用isActive/job?.isActive判断三、常见坑提醒不要用GlobalScope.launch做 UI 相关业务不要在Dispatchers.Default做阻塞 IO占满 CPU 线程池不要在协程里用Thread.sleep()改用delay()不要把suspend函数写在 UI 层直接切线程应由数据层负责