Coroutines + Retrofit + MVVM 简单实现
前言
- 这里基本不会深入讨论各个知识点,如需了解更多可以参考 Benny’s Blog 和 Kotlin官方文档
为什么使用协程
- 协程比线程小
我: 为什么已经有Rx了还要在这里用协程?
某同事: 因为协程比线程小,可以开很多个。
我:…… 为什么比线程小?
我维基了一下,确实有说比线程更小。
但是看了一些源码,也是线程池 + 线程实现的,这时就开始有了疑惑,为什么同样是线程,怎么就说是比线程小的东西呢?
直到看到了Benny大佬的文章 协程为什么被称为『轻量级线程』解释,我清晰了。通过测验,确实启动成千上万个协程也不会出现OOM或者其他问题。
- 当然最主要的还是代码上的体验
现在Kotlin越来越普遍,各种inline函数,操作符也都基本可以替换Rx的常用操作符了。所以在写代码上体验还是相对比较好的。
(备注:个人觉得协程小不小对于Android开发真的没多大区别,最主要还是写代码和代码美观性)
一、添加依赖
implementation"com.squareup.retrofit2:retrofit:2.6.2"
implementation"com.squareup.retrofit2:converter-gson:2.6.2"
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
记得使用retrofit 2.6.0 或以上
二、准备网络请求接口
- 创建接口
interface GitApi { @GET("/users/{username}/{module}") suspend fun repos( @Path("username") username: String, @Path("module") module: String, @Query("page") currPage: Int ): List<RepoInfo> @GET("/search/repositories") suspend fun searchRepos( @Query("q") key: String, @Query("sort") sort: String? = "updated", @Query("order") order: String? = "desc", @Query("page") currPage: Int ): SearchResponse }
- 创建Retrofit实例
fun buildRetrofit(): Retrofit { builder.addInterceptor(HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger { override fun log(message: String) { Timber.d(message) } }).apply { level = HttpLoggingInterceptor.Level.BODY }).addInterceptor(object : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val userCredentials = "$username:$password" val basicAuth = "Basic ${String(Base64.encode(userCredentials.toByteArray(), Base64.DEFAULT))}" val original = chain.request() val requestBuilder = original.newBuilder() .header("Authorization", basicAuth.trim { it <= ' ' }) val request = requestBuilder.build() return chain.proceed(request) } }) return Retrofit.Builder() .client(builder.build()) .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build() }
三、 在ViewModel中使用
kotlinx.coroutines.CoroutineScope
接口中的注释有这么一句话
所以启动一个协程一定需要一个Scope
,也就是说每个suspend函数都有一个CoroutineScope
,如果没有一定会报错。而Android的ViewModel中有扩展了一个viewModelScope
,并且跟lifecycle
绑定了,所以在ViewModel的onCleared方法上会自动帮我们cancel掉这个viewModelScope的所有Jobs。
为了方便使用,封装了一个BaseViewModel
open class BaseViewModel : ViewModel() {
fun <T> request(
onError: (error: Throwable) -> Unit = {}, // 不需要处理Error可以不传
execute: suspend CoroutineScope.() -> T
) {
viewModelScope.launch(errorHandler { onError.invoke(it) }) {
launch(Dispatchers.IO) {
execute()
}
}
}
private fun errorHandler(onError: (error: Throwable) -> Unit): CoroutineExceptionHandler {
return CoroutineExceptionHandler { _, throwable ->
Timber.d(throwable)
onError.invoke(throwable)
}
}
}
使用继承 BaseViewModel
调用 request
方法即可。
class RepoViewModel(
private val userRepo: UserDataSource,
private val gitApi: GitApi
) : BaseViewModel() {
private val _reposResult = BaseLiveData<List<RepoInfo>>()
val repoResult: BaseLiveData<List<RepoInfo>>
get() = _reposResult
fun fetchRepos(module: String) {
request {
userRepo.currUser()?.let {
val result = gitApi.repos(it.nickname, module, 1)
_reposResult.update(result)
}
}
}
}
OR
fun fetchRepos(module: String) {
request(
onError = {
// handle error
},
execute = {
userRepo.currUser()?.let {
val result = gitApi.repos(it.nickname, module, 1)
_reposResult.update(result)
}
}
)
}
剩下的基本是LiveData和Fragment之间的订阅上的逻辑实现了。
结语
最近在学习,了解也不是很深,欢迎评论补充和提建议。 学习项目地址 Dithub。