Retrofit + kotlin coroutines 使用
如何使用 Retrofit 和 coroutine 实现网络请求呢? 下面内容会做一个简单的介绍。
我们使用 ViewModel 处理网络请求,使用 LiveData 监听数据变化。
以下内容分为以下几部分:
- 依赖库版本版本要求
- 利用
suspend关键字定义API接口 - 在
ViewModel中使用coroutines发起网络请求 - 在
Activity和Fragment中通过observer观察liveData - 总结
1. 依赖库版本版本要求:
-
retrofit 2.6.0以上
changeLog
从changeLog上可以看到,从2.6.0以上,retrofit开始支持suspend关键字,这也是协程的关键。// 现在可以这么声明一个网络接口 @GET("users/{id}") suspend fun user(@Path("id") id: Long): User -
kotlin-coroutines-android
除了依赖kotlin外,需要引入协程相关库api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2' api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
2. 定义 API 接口
如上面所示,API 接口的样子为:
interface NewService {
/**
* 首页的 banner 的请求
*/
@GET("/banner/json")
suspend fun getBanner(): Response<HomeBanner>
@GET("/article/list/{page}/json")
suspend fun getArticleList(@Path("page") page: Int): Response<HomeListResponse>
}
上述接口为
https://www.wanandroid.com/的开放api
实用 suspend 关键字标注方法,并且返回类型为 Response 包裹的 所需要的数据类型.
和正常使用的 retrofit 接口唯一不同的地方是使用了 suspend 关键字。
suspend 是一个标志,告诉该函数的调用者,「这个函数」是一个耗时的操作,必须放入在协程中调用。
有关协程的仔细解析,我会放在另外一篇文章去写。
3. 在 ViewModel 中发起网络请求
首先在 Activity 或者 Fragment 中如何获取到 ViewModel
val firstHomeVM = ViewModelProviders.of(this).get(FirstHomeViewModel::class.java)
// 当需要获取数据时,如下
firstHomeVM.getBannerData()
在 FirstHomeViewModel 中 请求的网络访问:
以 getBannerData() 为例:
/**
* 获取首页 banner 信息
*/
fun getBannerData() {
// 第一处
viewModelScope.launch(IO) {
// 第二处
val result = getBannerUseCase.getWanAndroidBanner()
if (result is Result.Success) {
// 第三处
withContext(Main) {
emitUIBanner(result.data)
}
} else if (result is Result.Error) {
withContext(Main) {
emitUIEmptyBanner()
}
}
}
}
如代码所示, 我们标注了三处地方:「第一处」,「第二处」,「第三处」, 下面分析一下这几处的代码实现。
3.1 第一处:viewModelScope
首先,代码中 viewModelScope 来自 liftcycle-viewmodel-ktx-2.2.0 ,是 ViewModel 的一个扩展属性,当 ViewModel 被 cleared 时,viewModelScope 会被 cancel 掉。
在 kotlin 中,协程总是运行在以 CoroutineContext 类型为代表的上下文中。
在这个里面, launch() 方法有三个参数:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
...
}
前两个参数都有对应的默认值,这里我们传入的是一个闭包 block,在闭包中,是我们真正需要执行的逻辑。
-
CoroutineContext: 如果我们不传递,默认会运行在viewModelScope所在的线程 ->main线程, 在这里我们指定为IO线程。
3.2 第二处:getBannerUseCase.getWanAndroidBanner()
这里是获取网络数据的地方。
因为我们标明了 IO, 那么协成会帮我们切到切到子线程中执行 getWanAndroidBanner() ,
并且由于是挂起函数 suspend , 会在这个位置挂起当前线程,切到其他线程「主线程」做其他的事。
当结果 result 回来时,会继续执行下面的 if (result ...) 代码。
getWanAndroidBanner() 的本质上是进行了网络访问, 代码可简单写为
/**
* 获取 banner 信息
*/
suspend fun getWanAndroidBanner(): Result<List<HomeBanner.BannerItemData>> {
var result
val bannerResult = wanService.getBanner()
// 成功时
if (bannerResult.isSuccessful && bannerResult.body() != null) {
val body = bannerResult.body()
result = Result.Success(body)
} else {
result = Result.Error(Exception("获取 banner 失败 error code ${bannerResult.code()} error body is ${bannerResult.errorBody()} "))
}
// 再次处理 result
...
Log.i("zc_test", "hahahha current thread is ${Thread.currentThread()}")
return result
}
上述代码中的 Result 是我本地写的一个,统一对返回的结果进行了包装。
3.3 第三处:切换现场到主线程
withContext() 是 kotlinx-coroutines-core 中提供的一个 suspend 挂起函数,便于我们切换线程。
Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result.
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
)
在这里会挂起,并且执行特定的闭包「我们传入的 block」, 直到 block 运行完。
在我们的代码中:
withContext(Main) {
...
emitUIBanner(result.data)
}
// emitUIBanner(result.data) 的代码实现
/**
* 在 ui 现场中调用,刷新 banner
*/
UiThread
private fun emitUIBanner(banners: List<HomeBanner.BannerItemData>) {
bannerUILD.value = banners
}
我们切换该闭包在 Main 主线程中执行,里面的代码,本质上是刷新 UI 的逻辑。
利用 LiveData 的 setValue() 方法,通知外部可刷新 UI 了。
上面我们看到了 Main 和 IO, 是我们分别指定的协程调用器,协程会在指定的调度器上运行,并且会自动切换线程。
4. 在 Activity 和 Fragment 中通过 observer 观察 liveData
上面我们已经获取到了数据,并且通过 LiveData.setvalue() 设置了数据,那么我们得需要接收方拿到该数据后才会触动刷新操作。
在 Activity 和 Fragment 中通过 observer 观察 liveData.
代码如下:
// banner 成功的 监听
val bannerLDObserver = Observer<List<HomeBanner.BannerItemData>> {
// 刷新 UI
bannerList.addAll(it)
bannerDataList.addAll(it)
startSwitchJob()
bannerAdapter.notifyDataSetChanged()
// 刷新 UI
home_swipe_refresh.isRefreshing = false
}
firstHomeVM.bannerUILD.observe(this, bannerLDObserver)
通过上面四个步骤,我们实际上完成了「获取数据」和「更新 UI」的操作。
也是我们本次想要分享的内容,如果利用 kotlin coroutines 和 retrofit 实现网络请求。
5. 总结
上述简单的说明了,利用 kotlin coroutines 如何实现一些网络请求,对于耗时的一些操作我们都可以使用 kotlin coroutines 去实现。
至于为什么使用 kotlin coroutines 这里就不再讨论。
2019.12.5 by chendroid
水一下~
希望尽快详细写一篇有关协程的文章。
balabala









网友评论