1、前言
RxHttp
在v2.0版本中加入對協程的支持,收到了廣大kotlin用戶的喜愛,他們也不禁感慨,原來協程發請求還能如此優雅,比retrofit強大的不止一點點,然而,這就夠了嗎?遠遠不夠,為啥,因為還有痛點沒解決,為此,我也收集幾個目前網絡請求遇到的痛點,如下:- 異步操作,協程已為我們提供了async操作符處理異步問題,但用到時,每次還要包裝一次,不能接受
- 超時與重試,這種情況遇到的不多,但幾乎每個開發者都會遇到,真遇到時,如果沒有對應的API,也著實讓人著急
- 請求開始/結束延遲,這種情況也不多,但遇到的人也不少,自己處理著實麻煩
- 在請求並行中,假設有A、B兩個請求甚至更多,它們互不依賴,然而在協程中,如果A請求出現異常,那麼協程就中止了,此時B也跟著中止了,這是我們不想看到的結果,如何解決?常規的做法是對每個請求都做異常處理,使得出現異常,協程不會結束。但每個請求都需要單獨處理,寫起來著實會讓人抓破頭皮,這是很大的痛點
等等,其實還有很多小細節的問題,這裡就就不一一列舉了。
正因有以上問題,所以RxHttp v2.2.0版本就來了,該版本主要改動如下
- 新增一系列非常好用的操作符,如:asysn、timeout、retry、tryAwait等等
- 完全剔除RxJava,採用外掛方法替代,也正因如此,RxHttp做到同時支持RxJava2與RxJava3
- 將RxLieScope提取為單獨的一個庫,專門處理協程開啟/關閉/異常處理,本文後續會單獨介紹
gradle依賴
<code>dependencies { implementation'com.ljx.rxhttp:rxhttp:2.2.1'
kapt'com.ljx.rxhttp:rxhttp-compiler:2.2.1'
implementation'com.ljx.rxlife:rxlife-coroutine:2.0.0'
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'
}/<code>
注:
1、純Java項目,請使用annotationProcessor替代kapt;
2、依賴完,記得rebuild,才會生成RxHttp類
3、源碼地址:
https://github.com/liujingxing/okhttp-RxHttp
歡迎加入RxHttp&RxLife交流群:378530627
2、請求三部曲
相信還有之前沒了解過RxHttp的同學,這裡貼出RxHttp請求流程圖,記住該圖,你就掌握了RxHttp的精髓,如下:
代碼表示
<code>val str = RxHttp.get
("/service/..."
) .toStr() .await
() /<code>
怎麼樣,是不是非常簡單?
3、RxHttp操作符
3.1、retry 失敗重試
該操作符非常強大,不僅做到了失敗重試,還做到了週期性失敗重試,即間隔幾秒後重試,來看下完整的方法簽名
<code>fun
retry
( times:
Int
=Int
.MAX_VALUE, period:Long
=0
, test: ((Throwable
) ->Boolean
)? =null
)/<code>
retry()方法共有3個參數,分別是重試次數、重試周期、重試條件,都有默認值,3個參數可以隨意搭配,如:
<code>retry
()retry
(2
)retry
(2
,1000
)retry
{it
is
ConnectException
}retry
(2
) {it
is
ConnectException
}retry
(2
,1000
) {it
is
ConnectException
}retry
(period =1000
) {it
is
ConnectException
} /<code>
前兩個參數相信大家一看就能明白,這裡對第3個參數額外說一下,通過第三個參數,我們可以拿到Throwable異常對象,我們可以對異常做判斷,如果需要重試,就返回true,不需要就返回false,下面看看具體代碼
<code>val student = RxHttp.postForm("/service/..."
) .toClass() .retry(2
,1000
) { itis
ConnectException } .await
() /<code>
3.2、timeout 超時
OkHttp提供了全局的讀、寫及連接超時,有時我們也需要為某個請求設置不同的超時時長,此時就可以用到RxHttp的timeout(Long)
方法,如下<code>val student = RxHttp.postForm("/service/..."
) .toClass() .timeout(3000
) .await
() /<code>
3.3、async 異步操作符
如果我們有兩個請求需要並行時,就可以使用該操作符,如下:
<code>suspend
void initData() {val
asyncStudent1 = RxHttp.postForm("/service/..."
) .toClass() .async(this
)val
asyncStudent2 = RxHttp.postForm("/service/..."
) .toClass() .async(this
)val
student1 = asyncStudent1.await()val
student2 = asyncStudent2.await() } /<code>
3.4、delay、startDelay 延遲
delay操作符是請求結束後,延遲一段時間返回;而startDelay操作符則是延遲一段時間後再發送請求,如下:
<code>val student = RxHttp.postForm("/service/..."
) .toClass() .delay(1000
) .await
() val student = RxHttp.postForm("/service/..."
) .toClass() .startDelay(1000
) .await
() /<code>
3.5、onErrorReturn、onErrorReturnItem異常默認值
有些情況,我們不希望請求出現異常時,直接走異常回調,此時我們就可以通過兩個操作符,給出默認的值,如下:
<code>val
student = RxHttp.postForm("/service/..."
) .toClass() .timeout(100
) .onErrorReturn {return
@onErrorReturn
if
(itis
TimeoutCancellationException) Student()else
throw
it } .await()val
student = RxHttp.postForm("/service/..."
) .toClass() .timeout(100
) .onErrorReturnItem(Student()) .await()/<code>
3.6、tryAwait 異常返回null
如果你不想在異常時返回默認值,又不想異常是影響程序的執行,tryAwait就派上用場了,它會在異常出現時,返回null,如下:
<code>val
student = RxHttp.postForm("/service/..."
) .toClass() .timeout(100
) .tryAwait() /<code>
3.7、map 轉換符號
map操作符很好理解,RxJava即協程的Flow都有該操作符,功能都是一樣,用於轉換對象,如下:
<code>val student = RxHttp.postForm("/service/..."
) .toStr() .map
{ it.length } .tryAwait() /<code>
3.8、以上操作符隨意搭配
以上操作符,可隨意搭配使用,但調用順序的不同,產生的效果也不一樣,這裡悄悄告訴大家,以上操作符只會對上游代碼產生影響。
如timeout及retry:
<code>val student = RxHttp.postForm("/service/..."
) .toClass() .timeout(50
) .retry(2
,1000
) { itis
TimeoutCancellationException } .await
() /<code>
以上代碼,只要出現超時,就會重試,並且最多重試兩次。
但如果timeout、retry互換下位置,就不一樣了,如下:
<code>val student = RxHttp.postForm("/service/..."
) .toClass() .retry(2
,1000
) { itis
TimeoutCancellationException } .timeout(50
) .await
() /<code>
此時,如果50毫秒內請求沒有完成,就會觸發超時異常,並且直接走異常回調,不會重試。為什麼會這樣?原因很簡單,timeout及retry操作符,僅對上游代碼生效。如retry操作符,下游的異常是捕獲不到的,這就是為什麼timeout在retry下,超時時,重試機制沒有觸發的原因。
再看timeout和startDelay操作符
<code>val student = RxHttp.postForm("/service/..."
) .toClass() .startDelay(2000
) .timeout(1000
) .await
() /<code>
以上代碼,必定會觸發超時異常,因為startDelay,延遲了2000毫秒,而超時時長只有1000毫秒,所以必定觸發超時。
但互換下位置,又不一樣了,如下:
<code>val student = RxHttp.postForm("/service/..."
) .toClass() .timeout(1000
) .startDelay(2000
) .await
() /<code>
以上代碼正常情況下,都能正確拿到返回值,為什麼?原因很簡單,上面說過,操作符只會對上游產生影響,下游的startDelay延遲,它是不管的,也管不到。
4、協程開啟/關閉/異常處理
在以上示例中,我們統一用到await/tryAwait操作符獲取請求返回值,它們都是suspend
掛起函數,需要在另一個suspend掛起函數或者協程中才能被調用,故我們提供了RxLifeScope庫來處理協程開啟、關閉及異常處理,用法如下:4.1、在
FragemntActivity/Fragment/ViewModel環境下
在該環境下,直接調用rxLifeScope對象的lanuch方法開啟協程即可,如下:
<code>rxLifeScope.lanuch({ val student = RxHttp.postForm("/service/..."
) .toClass() .await
() }, { })/<code>
以上代碼,會在頁面銷燬時,自動關閉協程,同時自動關閉請求,無需擔心內存洩露問題
4.2、非
FragemntActivity/Fragment/ViewModel環境下
該環境下,我們需要手動創建RxLifeScope對象,隨後調用lanuch方法開啟協程
<code>val
job = RxLifeScope().lanuch({val
student = RxHttp.postForm("/service/..."
) .toClass() .await() }, { }) job.cancel()/<code>
以上代碼,由於未與生命週期綁定,故我們需要在合適的時機,手動關閉協程,協程關閉,請求也會跟著關閉
4.3、監聽協程開啟/結束回調
以上我們在lanuch方法,傳入協程運行回調及異常回調,我們也可以傳入協程開啟及結束回調,如下:
<code>rxLifeScope.launch({ val student = RxHttp.postForm("/service/..."
) .toClass() .await
() }, { }, { }, { }) /<code>
以上回調,均運行在UI線程
5、小結
可以看到,前面文章開頭提到超時/重試問題,就用timeout/retry,延遲就用delay/startDelay,出現異常不想中斷協程的運行,就用
onErrorReturn/onErrorReturnItem或者tryAwait,總之,一切都是那麼的優雅。
RxHttp的優雅遠不止這些,BaseUrl的處理,文件上傳/下載/進度監聽,緩存處理、業務code統一判斷等等,處理的都令人歎為觀止,
更多功能查看以下文章
更多協程用法:
https://juejin.im/editor/posts/5e77604fe51d4527066eb81a
RxHttp非協程用法:
https://juejin.im/post/5ded221a518825125d14a1d4
最後,開源不易,寫文章更不易,如果你覺得不錯RxHttp或給你帶來了幫助,歡迎點贊收藏,以備不時之需,如果可以,再給個star(源碼地址:
https://github.com/liujingxing/okhttp-RxHttp),我將感激不盡,