Retrofit + kotlin coroutines 使用
如何使用 Retrofit
和 coroutine
實(shí)現(xiàn)網(wǎng)絡(luò)請求呢? 下面內(nèi)容會做一個簡單的介紹贩疙。
我們使用 ViewModel
處理網(wǎng)絡(luò)請求,使用 LiveData
監(jiān)聽數(shù)據(jù)變化况既。
以下內(nèi)容分為以下幾部分:
- 依賴庫版本版本要求
- 利用
suspend
關(guān)鍵字定義API
接口 - 在
ViewModel
中使用coroutines
發(fā)起網(wǎng)絡(luò)請求 - 在
Activity
和Fragment
中通過observer
觀察liveData
- 總結(jié)
1. 依賴庫版本版本要求:
-
retrofit 2.6.0
以上
changeLog
從changeLog
上可以看到这溅,從2.6.0
以上,retrofit
開始支持suspend
關(guān)鍵字棒仍,這也是協(xié)程的關(guān)鍵悲靴。// 現(xiàn)在可以這么聲明一個網(wǎng)絡(luò)接口 @GET("users/{id}") suspend fun user(@Path("id") id: Long): User
-
kotlin-coroutines-android
除了依賴kotlin
外,需要引入?yún)f(xié)程相關(guān)庫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
實(shí)用 suspend
關(guān)鍵字標(biāo)注方法癞尚,并且返回類型為 Response
包裹的 所需要的數(shù)據(jù)類型
.
和正常使用的 retrofit
接口唯一不同的地方是使用了 suspend
關(guān)鍵字。
suspend
是一個標(biāo)志乱陡,告訴該函數(shù)的調(diào)用者浇揩,「這個函數(shù)」是一個耗時的操作,必須放入在協(xié)程中調(diào)用憨颠。
有關(guān)協(xié)程的仔細(xì)解析临燃,我會放在另外一篇文章去寫。
3. 在 ViewModel
中發(fā)起網(wǎng)絡(luò)請求
首先在 Activity
或者 Fragment
中如何獲取到 ViewModel
val firstHomeVM = ViewModelProviders.of(this).get(FirstHomeViewModel::class.java)
// 當(dāng)需要獲取數(shù)據(jù)時烙心,如下
firstHomeVM.getBannerData()
在 FirstHomeViewModel
中 請求的網(wǎng)絡(luò)訪問:
以 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()
}
}
}
}
如代碼所示, 我們標(biāo)注了三處地方:「第一處」,「第二處」乏沸,「第三處」, 下面分析一下這幾處的代碼實(shí)現(xiàn)淫茵。
3.1 第一處:viewModelScope
首先,代碼中 viewModelScope
來自 liftcycle-viewmodel-ktx-2.2.0
蹬跃,是 ViewModel
的一個擴(kuò)展屬性匙瘪,當(dāng) ViewModel
被 cleared
時铆铆,viewModelScope
會被 cancel
掉。
在 kotlin
中丹喻,協(xié)程總是運(yùn)行在以 CoroutineContext
類型為代表的上下文中薄货。
在這個里面, launch()
方法有三個參數(shù):
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
...
}
前兩個參數(shù)都有對應(yīng)的默認(rèn)值碍论,這里我們傳入的是一個閉包 block
谅猾,在閉包中,是我們真正需要執(zhí)行的邏輯鳍悠。
-
CoroutineContext
: 如果我們不傳遞税娜,默認(rèn)會運(yùn)行在viewModelScope
所在的線程 ->main
線程, 在這里我們指定為IO
線程。
3.2 第二處:getBannerUseCase.getWanAndroidBanner()
這里是獲取網(wǎng)絡(luò)數(shù)據(jù)的地方藏研。
因為我們標(biāo)明了 IO
敬矩, 那么協(xié)成會幫我們切到切到子線程中執(zhí)行 getWanAndroidBanner()
,
并且由于是掛起函數(shù) suspend
蠢挡, 會在這個位置掛起當(dāng)前線程弧岳,切到其他線程「主線程」做其他的事。
當(dāng)結(jié)果 result
回來時业踏,會繼續(xù)執(zhí)行下面的 if (result ...)
代碼禽炬。
getWanAndroidBanner()
的本質(zhì)上是進(jìn)行了網(wǎng)絡(luò)訪問, 代碼可簡單寫為
/**
* 獲取 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
是我本地寫的一個堡称,統(tǒng)一對返回的結(jié)果進(jìn)行了包裝瞎抛。
3.3 第三處:切換現(xiàn)場到主線程
withContext()
是 kotlinx-coroutines-core
中提供的一個 suspend
掛起函數(shù),便于我們切換線程却紧。
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
)
在這里會掛起桐臊,并且執(zhí)行特定的閉包「我們傳入的 block
」, 直到 block
運(yùn)行完。
在我們的代碼中:
withContext(Main) {
...
emitUIBanner(result.data)
}
// emitUIBanner(result.data) 的代碼實(shí)現(xiàn)
/**
* 在 ui 現(xiàn)場中調(diào)用晓殊,刷新 banner
*/
UiThread
private fun emitUIBanner(banners: List<HomeBanner.BannerItemData>) {
bannerUILD.value = banners
}
我們切換該閉包在 Main
主線程中執(zhí)行断凶,里面的代碼,本質(zhì)上是刷新 UI
的邏輯巫俺。
利用 LiveData
的 setValue()
方法认烁,通知外部可刷新 UI
了。
上面我們看到了 Main
和 IO
介汹, 是我們分別指定的協(xié)程調(diào)用器却嗡,協(xié)程會在指定的調(diào)度器上運(yùn)行,并且會自動切換線程嘹承。
4. 在 Activity
和 Fragment
中通過 observer
觀察 liveData
上面我們已經(jīng)獲取到了數(shù)據(jù)窗价,并且通過 LiveData.setvalue()
設(shè)置了數(shù)據(jù),那么我們得需要接收方拿到該數(shù)據(jù)后才會觸動刷新操作叹卷。
在 Activity
和 Fragment
中通過 observer
觀察 liveData
.
代碼如下:
// banner 成功的 監(jiān)聽
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)
通過上面四個步驟撼港,我們實(shí)際上完成了「獲取數(shù)據(jù)」和「更新 UI
」的操作坪它。
也是我們本次想要分享的內(nèi)容,如果利用 kotlin coroutines
和 retrofit
實(shí)現(xiàn)網(wǎng)絡(luò)請求帝牡。
5. 總結(jié)
上述簡單的說明了往毡,利用 kotlin coroutines
如何實(shí)現(xiàn)一些網(wǎng)絡(luò)請求,對于耗時的一些操作我們都可以使用 kotlin coroutines
去實(shí)現(xiàn)靶溜。
至于為什么使用 kotlin coroutines
這里就不再討論开瞭。
2019.12.5 by chendroid
水一下~
希望盡快詳細(xì)寫一篇有關(guān)協(xié)程的文章。
balabala