做安卓开发的应该都遇到过这种情况:点个按钮加载数据,界面卡住几秒,用户以为手机坏了。以前用线程切换来回折腾,代码嵌套得像迷宫。后来用了Kotlin协程,写异步代码跟写同步一样顺滑,尤其是结合实际项目,效果特别明显。
一个天气App的小例子
假设你正在做一个简单的天气应用,用户打开页面时,需要从网络获取当前城市气温,同时加载本地缓存的穿衣建议。这两个操作互不依赖,完全可以并行处理。
用传统的回调方式,你得开两个线程,再在主线程更新UI,一不小心就出错。而用协程,几行代码就能搞定:
viewModelScope.launch {
val weatherDeferred = async { repository.fetchWeather() }
val suggestionDeferred = async { repository.loadSuggestionFromLocal() }
val weather = weatherDeferred.await()
val suggestion = suggestionDeferred.await()
updateUi(weather, suggestion)
}
这里用了 async 启动两个并发任务,await() 等结果回来。整个过程清晰明了,没有层层嵌套,也不会阻塞主线程。
处理异常也很自然
网络请求总可能失败。协程里可以直接用 try-catch 包住 launch 或 async 块,不用额外定义回调接口。
viewModelScope.launch {
try {
val data = async { apiService.getData() }.await()
showData(data)
} catch (e: Exception) {
showError("加载失败,请检查网络")
}
}
这种写法读起来就像在讲人话:试着去拿数据,拿到了就展示,出错了就提示。调试的时候也容易定位问题在哪一行。
实际项目中的结构建议
在真实项目中,别把所有协程逻辑堆在 Activity 或 Fragment 里。推荐把数据操作放在 Repository 层,用 suspend 函数封装:
class WeatherRepository {
suspend fun getWeatherAndSuggestion(city: String): Result {
return withContext(Dispatchers.IO) {
val weather = fetchWeather(city)
val suggestion = loadSuggestion(city)
Result(weather, suggestion)
}
}
private suspend fun fetchWeather(city: String): Weather { ... }
private suspend fun loadSuggestion(city: String): Suggestion { ... }
}
ViewModel 里调用这个函数时,仍然是简洁的 suspend 调用,然后通过 StateFlow 或 LiveData 更新界面。这样分层清楚,测试也方便。
别忘了取消协程
用户点进页面开始加载,还没等结果就按返回键走了——这时候还在跑的协程得及时取消,不然可能造成内存泄漏或者空指针。用 viewModelScope 就很省心,它会随着 ViewModel 销毁自动取消所有子协程,不需要手动干预。
如果你自己创建 CoroutineScope,记得加上 Job 和超时控制:
val scope = CoroutineScope(Dispatchers.Main + Job())
// 页面销毁时调用
fun onDestroy() {
scope.cancel()
}
协程不是魔法,但用对了真能少写很多“防御性代码”。现在新项目基本都默认上协程了,老项目也在逐步迁移。与其每次遇到异步就头疼,不如找个简单功能先练手,比如把登录请求改成 suspend 函数试试。