1褪那、前言
RxHttp 在v2.0版本中加入對(duì)協(xié)程的支持幽纷,收到了廣大kotlin用戶的喜愛,他們也不禁感慨博敬,原來協(xié)程發(fā)請(qǐng)求還能如此優(yōu)雅友浸,比retrofit強(qiáng)大的不止一點(diǎn)點(diǎn),然而偏窝,這就夠了嗎收恢?遠(yuǎn)遠(yuǎn)不夠,為啥囚枪,因?yàn)檫€有痛點(diǎn)沒解決派诬,為此,我也收集幾個(gè)目前網(wǎng)絡(luò)請(qǐng)求遇到的痛點(diǎn)链沼,如下:
- 異步操作默赂,協(xié)程已為我們提供了
async
操作符處理異步問題,但用到時(shí)括勺,每次還要包裝一次缆八,不能接受 - 超時(shí)與重試,這種情況遇到的不多疾捍,但幾乎每個(gè)開發(fā)者都會(huì)遇到奈辰,真遇到時(shí),如果沒有對(duì)應(yīng)的API乱豆,也著實(shí)讓人著急
- 請(qǐng)求開始/結(jié)束延遲奖恰,這種情況也不多,但遇到的人也不少宛裕,自己處理著實(shí)麻煩
- 在請(qǐng)求并行中瑟啃,假設(shè)有A、B兩個(gè)請(qǐng)求甚至更多揩尸,它們互不依賴蛹屿,然而在協(xié)程中,如果A請(qǐng)求出現(xiàn)異常岩榆,那么協(xié)程就中止了错负,此時(shí)B也跟著中止了,這是我們不想看到的結(jié)果勇边,如何解決犹撒?常規(guī)的做法是對(duì)每個(gè)請(qǐng)求都做異常處理,使得出現(xiàn)異常粒褒,協(xié)程不會(huì)結(jié)束识颊。但每個(gè)請(qǐng)求都需要單獨(dú)處理,寫起來著實(shí)會(huì)讓人抓破頭皮怀浆,這是很大的痛點(diǎn)
等等谊囚,其實(shí)還有很多小細(xì)節(jié)的問題,這里就就不一一列舉了执赡。
正因有以上問題镰踏,所以RxHttp v2.2.0版本就來了,該版本主要改動(dòng)如下
- 新增一系列非常好用的操作符沙合,如:
asysn
奠伪、timeout
、retry
首懈、tryAwait
等等 - 完全剔除RxJava绊率,采用外掛方法替代,也正因如此究履,RxHttp做到同時(shí)支持RxJava2與RxJava3
- 將RxLieScope提取為單獨(dú)的一個(gè)庫滤否,專門處理協(xié)程開啟/關(guān)閉/異常處理,本文后續(xù)會(huì)單獨(dú)介紹
gradle依賴
dependencies {
//必須
implementation 'com.ljx.rxhttp:rxhttp:2.2.1'
kapt 'com.ljx.rxhttp:rxhttp-compiler:2.2.1' //生成RxHttp類
//以下均為非必須
//管理協(xié)程生命周期最仑,頁面銷毀藐俺,關(guān)閉請(qǐng)求
implementation 'com.ljx.rxlife:rxlife-coroutine:2.0.0'
//Converter 根據(jù)自己需求選擇 RxHttp默認(rèn)內(nèi)置了GsonConverter
implementation 'com.ljx.rxhttp:converter-jackson:2.2.1'
implementation 'com.ljx.rxhttp:converter-fastjson:2.2.1'
implementation 'com.ljx.rxhttp:converter-protobuf:2.2.1'
implementation 'com.ljx.rxhttp:converter-simplexml:2.2.1'
}
注:純Java項(xiàng)目,請(qǐng)使用annotationProcessor替代kapt泥彤;依賴完欲芹,記得rebuild,才會(huì)生成RxHttp類
歡迎加入RxHttp&RxLife交流群:378530627
2吟吝、請(qǐng)求三部曲
相信還有沒了解過RxHttp的同學(xué)菱父,這里貼出RxHttp請(qǐng)求流程圖,記住該圖剑逃,你就掌握了RxHttp的精髓浙宜,如下:
代碼表示
val str = RxHttp.get("/service/...") //第一步,確定請(qǐng)求方式炕贵,可以選擇postForm梆奈、postJson等方法
.toStr() //第二步,確認(rèn)返回類型称开,這里代表返回String類型
.await() //第二步亩钟,使用await方法拿到返回值
怎么樣,是不是非常簡(jiǎn)單鳖轰?
3清酥、RxHttp操作符
3.1、retry 失敗重試
該操作符非常強(qiáng)大蕴侣,不僅做到了失敗重試焰轻,還做到了周期性失敗重試,即間隔幾秒后重試昆雀,來看下完整的方法簽名
/**
* 失敗重試辱志,該方法僅在使用協(xié)程時(shí)才有效
* @param times 重試次數(shù), 默認(rèn)Int.MAX_VALUE 代表不斷重試
* @param period 重試周期, 默認(rèn)為0, 單位: milliseconds
* @param test 重試條件, 默認(rèn)為空蝠筑,即無條件重試
*/
fun retry(
times: Int = Int.MAX_VALUE,
period: Long = 0,
test: ((Throwable) -> Boolean)? = null
)
retry()
方法共有3個(gè)參數(shù),分別是重試次數(shù)揩懒、重試周期什乙、重試條件,都有默認(rèn)值已球,3個(gè)參數(shù)可以隨意搭配臣镣,如:
retry() //無條件、不間斷智亮、一直重試
retry(2) //無條件忆某、不間斷、重試2次
retry(2, 1000) //無條件 間隔1s 重試2次
retry { it is ConnectException } //有條件阔蛉、不間斷弃舒、一直重試
retry(2) { it is ConnectException } //有條件、不間斷馍忽、重試2次
retry(2, 1000) { it is ConnectException } //有條件棒坏、間隔1s、重試2次
retry(period = 1000) { it is ConnectException } //有條件遭笋、間斷1s坝冕、一直重試
前兩個(gè)參數(shù)相信大家一看就能明白,這里對(duì)第3個(gè)參數(shù)額外說一下瓦呼,通過第三個(gè)參數(shù)喂窟,我們可以拿到Throwable
異常對(duì)象,我們可以對(duì)異常做判斷央串,如果需要重試磨澡,就返回true,不需要就返回false质和,下面看看具體代碼
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.retry(2, 1000) { //重試2次稳摄,每次間隔1s
it is ConnectException //如果是網(wǎng)絡(luò)異常就重試
}
.await()
3.2、timeout 超時(shí)
OkHttp提供了全局的讀饲宿、寫及連接超時(shí)厦酬,有時(shí)我們也需要為某個(gè)請(qǐng)求設(shè)置不同的超時(shí)時(shí)長,此時(shí)就可以用到RxHttp的timeout(Long)
方法瘫想,如下:
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.timeout(3000) //超時(shí)時(shí)長為3s
.await()
3.3仗阅、async 異步操作符
如果我們有兩個(gè)請(qǐng)求需要并行時(shí),就可以使用該操作符国夜,如下:
//同時(shí)獲取兩個(gè)學(xué)生信息
suspend void initData() {
val asyncStudent1 = RxHttp.postForm("/service/...")
.toClass<Student>()
.async(this) //this為CoroutineScope對(duì)象减噪,這里會(huì)返回Deferred<Student>
val asyncStudent2 = RxHttp.postForm("/service/...")
.toClass<Student>()
.async(this) //this為CoroutineScope對(duì)象,這里會(huì)返回Deferred<Student>
//隨后調(diào)用await方法獲取對(duì)象
val student1 = asyncStudent1.await()
val student2 = asyncStudent2.await()
}
3.4、delay筹裕、startDelay 延遲
delay
操作符是請(qǐng)求結(jié)束后醋闭,延遲一段時(shí)間返回;而startDelay
操作符則是延遲一段時(shí)間后再發(fā)送請(qǐng)求朝卒,如下:
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.delay(1000) //請(qǐng)求回來后目尖,延遲1s返回
.await()
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.startDelay(1000) //延遲1s后再發(fā)送請(qǐng)求
.await()
3.5、onErrorReturn扎运、onErrorReturnItem異常默認(rèn)值
有些情況,我們不希望請(qǐng)求出現(xiàn)異常時(shí)饮戳,直接走異澈乐危回調(diào),此時(shí)我們就可以通過兩個(gè)操作符扯罐,給出默認(rèn)的值负拟,如下:
//根據(jù)異常給出默認(rèn)值
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.timeout(100) //超時(shí)時(shí)長為100毫秒
.onErrorReturn {
//如果是超時(shí)異常,就給出默認(rèn)值歹河,否則掩浙,拋出原異常
return@onErrorReturn if (it is TimeoutCancellationException)
Student()
else
throw it
}
.await()
//只要出現(xiàn)異常,就返回默認(rèn)值
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.timeout(100) //超時(shí)時(shí)長為100毫秒
.onErrorReturnItem(Student())
.await()
3.6秸歧、tryAwait 異常返回null
如果你不想在異常時(shí)返回默認(rèn)值厨姚,又不想異常是影響程序的執(zhí)行,tryAwait
就派上用場(chǎng)了键菱,它會(huì)在異常出現(xiàn)時(shí)谬墙,返回null,如下:
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.timeout(100) //超時(shí)時(shí)長為100毫秒
.tryAwait() //這里返回 Student? 對(duì)象经备,即有可能為空
3.7拭抬、map 轉(zhuǎn)換符號(hào)
map
操作符很好理解,RxJava即協(xié)程的Flow都有該操作符侵蒙,功能都是一樣造虎,用于轉(zhuǎn)換對(duì)象,如下:
val student = RxHttp.postForm("/service/...")
.toStr()
.map { it.length } //String轉(zhuǎn)Int
.tryAwait() //這里返回 Student? 對(duì)象纷闺,即有可能為空
3.8算凿、以上操作符隨意搭配
以上操作符,可隨意搭配使用急但,但調(diào)用順序的不同澎媒,產(chǎn)生的效果也不一樣,這里悄悄告訴大家波桩,以上操作符只會(huì)對(duì)上游代碼產(chǎn)生影響戒努。
如timeout及retry
:
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.timeout(50)
.retry(2, 1000) { it is TimeoutCancellationException }
.await()
以上代碼,只要出現(xiàn)超時(shí),就會(huì)重試储玫,并且最多重試兩次侍筛。
但如果timeout
、retry
互換下位置撒穷,就不一樣了匣椰,如下:
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.retry(2, 1000) { it is TimeoutCancellationException }
.timeout(50)
.await()
此時(shí),如果50毫秒內(nèi)請(qǐng)求沒有完成端礼,就會(huì)觸發(fā)超時(shí)異常禽笑,并且直接走異常回調(diào)蛤奥,不會(huì)重試佳镜。為什么會(huì)這樣?原因很簡(jiǎn)單凡桥,timeout及retry
操作符蟀伸,僅對(duì)上游代碼生效。如retry操作符缅刽,下游的異常是捕獲不到的啊掏,這就是為什么timeout在retry下,超時(shí)時(shí)衰猛,重試機(jī)制沒有觸發(fā)的原因迟蜜。
再看timeout
和startDelay
操作符
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.startDelay(2000)
.timeout(1000)
.await()
以上代碼,必定會(huì)觸發(fā)超時(shí)異常啡省,因?yàn)閟tartDelay小泉,延遲了2000毫秒,而超時(shí)時(shí)長只有1000毫秒冕杠,所以必定觸發(fā)超時(shí)微姊。
但互換下位置,又不一樣了分预,如下:
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.timeout(1000)
.startDelay(2000)
.await()
以上代碼正常情況下兢交,都能正確拿到返回值,為什么笼痹?原因很簡(jiǎn)單配喳,上面說過,操作符只會(huì)對(duì)上游產(chǎn)生影響凳干,下游的startDelay
延遲晴裹,它是不管的,也管不到救赐。
4涧团、協(xié)程開啟/關(guān)閉/異常處理
在以上示例中,我們統(tǒng)一用到await/tryAwait
操作符獲取請(qǐng)求返回值,它們都是suspend
掛起函數(shù)泌绣,需要在另一個(gè)suspend
掛起函數(shù)或者協(xié)程中才能被調(diào)用钮追,故我們提供了RxLifeScope庫來處理協(xié)程開啟、關(guān)閉及異常處理阿迈,用法如下:
在FragemntActivity/Fragment/ViewModel環(huán)境下
在該環(huán)境下元媚,直接調(diào)用rxLifeScope
對(duì)象的lanuch
方法開啟協(xié)程即可,如下:
rxLifeScope.lanuch({
//協(xié)程代碼塊苗沧,運(yùn)行在UI線程
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.await()
//可直接更新UI
}, {
//異晨兀回調(diào),這里可以拿到Throwable對(duì)象
})
以上代碼待逞,會(huì)在頁面銷毀時(shí)鞠绰,自動(dòng)關(guān)閉協(xié)程,同時(shí)自動(dòng)關(guān)閉請(qǐng)求飒焦,無需擔(dān)心內(nèi)存泄露問題
非FragemntActivity/Fragment/ViewModel環(huán)境下
該環(huán)境下,我們需要手動(dòng)創(chuàng)建RxLifeScope
對(duì)象屿笼,隨后調(diào)用lanuch
方法開啟協(xié)程
val job = RxLifeScope().lanuch({
//協(xié)程代碼塊牺荠,運(yùn)行在UI線程
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.await()
//可直接更新UI
}, {
//異常回調(diào)驴一,這里可以拿到Throwable對(duì)象
})
//在合適的時(shí)機(jī)關(guān)閉協(xié)程
job.cancel()
以上代碼休雌,由于未與生命周期綁定,故我們需要在合適的時(shí)機(jī)肝断,手動(dòng)關(guān)閉協(xié)程杈曲,協(xié)程關(guān)閉,請(qǐng)求也會(huì)跟著關(guān)閉
監(jiān)聽協(xié)程開啟/結(jié)束回調(diào)
以上我們?cè)?code>lanuch方法胸懈,傳入?yún)f(xié)程運(yùn)行回調(diào)及異车F耍回調(diào),我們也可以傳入?yún)f(xié)程開啟及結(jié)束回調(diào)趣钱,如下:
rxLifeScope.launch({
//協(xié)程代碼塊
val student = RxHttp.postForm("/service/...")
.toClass<Student>()
.await()
//可直接更新UI
}, {
//異秤肯祝回調(diào),這里可以拿到Throwable對(duì)象首有,運(yùn)行在UI線程
}, {
//開始回調(diào)燕垃,可以開啟等待彈窗,運(yùn)行在UI線程
}, {
//結(jié)束回調(diào)井联,可以銷毀等待彈窗卜壕,運(yùn)行在UI線程
})
以上回調(diào),均運(yùn)行在UI線程
5烙常、小結(jié)
可以看到轴捎,前面文章開頭提到超時(shí)/重試問題,就用timeout/retry
,延遲就用delay/startDelay
轮蜕,出現(xiàn)異常不想中斷協(xié)程的運(yùn)行昨悼,就用onErrorReturn/onErrorReturnItem
或者tryAwait
,總之跃洛,一切都是那么的優(yōu)雅率触。
RxHttp的優(yōu)雅遠(yuǎn)不止這些,BaseUrl的處理汇竭,文件上傳/下載/進(jìn)度監(jiān)聽葱蝗,緩存處理、業(yè)務(wù)code統(tǒng)一判斷等等细燎,處理的都令人嘆為觀止两曼,
更多功能查看以下文章
協(xié)程用法:RxHttp ,比Retrofit 更優(yōu)雅的協(xié)程體驗(yàn)
RxJava用法:RxHttp 讓你眼前一亮的Http請(qǐng)求框架
最后玻驻,開源不易悼凑,寫文章更不易,如果你覺得不錯(cuò)RxHttp或給你帶來了幫助璧瞬,歡迎點(diǎn)贊收藏户辫,以備不時(shí)之需,如果可以嗤锉,再給個(gè)star渔欢,我將感激不盡,????????????????????????