协程启动了但没执行?检查作用域是否存活
在实际开发中,经常会遇到这样的情况:点击按钮发起网络请求,用的是 launch 启动协程,但断点根本进不去。这时候别急着怀疑网络,先看看你的 CoroutineScope 还在不在。
比如 Activity 里定义了一个 MainScope,在 onResume 里启动协程,但用户快速退出页面,Activity 被销毁,scope 也被取消。如果协程还没来得及执行,就会被直接 cancel。解决办法是把 scope 绑定到生命周期更长的组件上,或者使用 viewModelScope(在 ViewModel 中)。
class MyViewModel : ViewModel() {
fun fetchData() {
viewModelScope.launch {
try {
val data = repository.getData()
// 更新 UI
} catch (e: Exception) {
// 处理异常
}
}
}
}多个并发请求变成串行?可能是用了错误的等待方式
有次做商品详情页,要同时拉取商品信息、用户评论和推荐列表。原本想用 async 并发三个请求,结果发现总耗时接近三者相加。查了一圈才发现,是 await 写在了 launch 里,而且顺序调用。
正确做法是先启动所有 async 任务,再统一 await。这样才能真正并行:
viewModelScope.launch {
val productDeferred = async { repository.getProduct(id) }
val commentDeferred = async { repository.getComments(id) }
val recommendDeferred = async { repository.getRecommendations(id) }
val product = productDeferred.await()
val comments = commentDeferred.await()
val recommendations = recommendDeferred.await()
}主线程卡顿?别在协程里做耗时同步操作
有个同事在 withContext(Dispatchers.IO) 里读取一个大文件,按理说不会影响 UI。但用户反馈页面滑动卡顿。后来发现他用的是 FileInputStream.readBytes(),这个方法在大文件时会占用大量时间,虽然在 IO 线程,但线程池被占满,其他任务排队。
建议对特别耗时的操作拆分处理,或者使用流式读取,避免长时间阻塞线程。
异常 silent fail?记得处理协程内部异常
launch 默认不抛出异常,一旦内部发生错误,程序就静默失败。比如上传日志的功能,失败了也没提示,数据就丢了。
可以加上 try-catch,或者使用 SupervisorJob 来控制异常传播:
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
scope.launch {
try {
uploadLogs()
} catch (e: IOException) {
Log.e("Upload", "Failed to upload logs", e)
}
}重复请求停不下来?取消旧任务很关键
搜索框输入时自动联想,每输入一个字符就发起一次请求。如果网络慢,用户连续输入,可能收到乱序返回的结果。比如先发“手机”,再发“手机壳”,结果“手机”的结果后到,界面又刷回去了。
应该在每次发起新请求前取消之前的任务。可以用 Job 变量保存当前任务:
var searchJob: Job? = null
fun performSearch(query: String) {
searchJob?.cancel()
searchJob = viewModelScope.launch {
try {
val results = repository.search(query)
updateUi(results)
} catch (e: CancellationException) {
// 忽略取消异常
}
}
}