为什么用协程处理Android异步任务
在开发Android应用时,经常要从网络拉数据、读写数据库,或者做些耗时的计算。以前常用Handler、AsyncTask甚至开Thread,代码嵌套多,回调层层叠,维护起来头疼。自从Kotlin协程推出后,异步操作变得像写同步代码一样清晰。
比如你做个天气App,点“刷新”要从服务器取最新数据,同时更新UI。用传统方式得切线程来回跳,协程几行代码就搞定。
基础概念:CoroutineScope、launch和async
协程运行需要一个作用域(CoroutineScope),Android中通常用lifecycleScope或viewModelScope,它们会自动管理生命周期,避免内存泄漏。
lifecycleScope.launch {
val weatherData = async(Dispatchers.IO) {
repository.fetchWeather()
}
updateUi(weatherData.await())
}上面这段代码在按钮点击后启动协程,async开启一个IO线程去请求数据,主线程等待结果后更新界面。整个过程线性表达,逻辑一目了然。
切换线程就这么简单
协程通过Dispatchers控制运行线程。常见有三个:
- Dispatchers.Main:用于更新UI
- Dispatchers.IO:适合读写文件、网络请求
- Dispatchers.Default:适合CPU密集型计算
比如你想先查本地缓存,没有再走网络,可以这样写:
lifecycleScope.launch {
val data = withContext(Dispatchers.IO) {
val cached = db.weatherDao().getLast()
if (cached != null && !isExpired(cached)) {
cached
} else {
val remote = api.getLatestWeather()
db.weatherDao().insert(remote)
remote
}
}
binding.tempText.text = "${data.temp}°C"
}withContext直接切换到IO线程执行数据库和网络操作,结束后自动回到原线程更新UI,不用手动post。
多个任务并行怎么做
假如你的首页要同时加载用户信息、通知数量和配置项,三个接口互不依赖,就可以并行发起。
lifecycleScope.launch {
val userDeferred = async { userRepository.getUser() }
val notifyDeferred = async { notifyRepository.getCount() }
val configDeferred = async { configRepository.load() }
val user = userDeferred.await()
val notifyCount = notifyDeferred.await()
val config = configDeferred.await()
renderHome(user, notifyCount, config)
}三个async同时开始,各自跑在自己的线程里,await时才阻塞等待结果。整体耗时等于最慢的那个请求,效率提升明显。
异常处理别忘了
协程里抛异常不会自动冒泡到主线程,得自己捕获。推荐用try-catch包住launch里的逻辑。
lifecycleScope.launch {
try {
val result = api.getData()
showSuccess(result)
} catch (e: Exception) {
showError("加载失败,请重试")
}
}如果多个子协程都要统一处理错误,可以用SupervisorJob配合CoroutineExceptionHandler。
结合ViewModel更顺手
在MVVM架构中,ViewModel里用viewModelScope发请求最合适。页面销毁时,协程自动取消,不会造成崩溃。
class MainViewModel : ViewModel() {
fun loadUserData() {
viewModelScope.launch {
try {
val user = userRepository.fetch()
_uiState.value = UserLoaded(user)
} catch (e: IOException) {
_uiState.value = LoadFailed
}
}
}
}Activity或Fragment观察_uiState即可更新界面,完全不用操心协程生命周期。
实际场景:下拉刷新+加载更多
列表页很常见既要支持刷新,又要分页加载。两个操作都用协程处理,互不干扰。
// 下拉刷新
binding.swipeRefresh.setOnRefreshListener {
lifecycleScope.launch {
refreshData()
binding.swipeRefresh.isRefreshing = false
}
}
// 滑动加载
adapter.onLoadMore {
lifecycleScope.launch {
val newItems = fetchNextPage()
adapter.addItems(newItems)
}
}每个操作独立启协程,即使用户快速连点刷新,也不会堆叠多个网络请求。