可怕!RxHttp2.0重大更新!協程發請求,原來如此簡單

一、前言

RxHttp 在v2.0版本中加入對協程的支持,收到了廣大kotlin用戶的喜好,他們也不由感慨,原來協程發請求還能如此優雅,比retrofit強大的不止一點點,然而,這就夠了嗎?遠遠不夠,爲啥,由於還有痛點沒解決,爲此,我也收集幾個目前網絡請求遇到的痛點,以下:java

  • 異步操做,協程已爲咱們提供了async操做符處理異步問題,但用到時,每次還要包裝一次,不能接受
  • 超時與重試,這種狀況遇到的很少,但幾乎每一個開發者都會遇到,真遇到時,若是沒有對應的API,也着實讓人着急
  • 請求開始/結束延遲,這種狀況也很少,但遇到的人也很多,本身處理着實麻煩
  • 在請求並行中,假設有A、B兩個請求甚至更多,它們互不依賴,然而在協程中,若是A請求出現異常,那麼協程就停止了,此時B也跟着停止了,這是咱們不想看到的結果,如何解決?常規的作法是對每一個請求都作異常處理,使得出現異常,協程不會結束。但每一個請求都須要單獨處理,寫起來着實會讓人抓破頭皮,這是很大的痛點

等等,其實還有不少小細節的問題,這裏就就不一一列舉了。git

正因有以上問題,因此RxHttp v2.2.0版本就來了,該版本主要改動以下github

  • 新增一系列很是好用的操做符,如:asysntimeoutretrytryAwait等等
  • 徹底剔除RxJava,採用外掛方法替代,也正因如此,RxHttp作到同時支持RxJava2與RxJava3
  • RxLieScope提取爲單獨的一個庫,專門處理協程開啓/關閉/異常處理,本文後續會單獨介紹

gradle依賴json

dependencies {
   //必須
   implementation 'com.ljx.rxhttp:rxhttp:2.2.0'
   kapt 'com.ljx.rxhttp:rxhttp-compiler:2.2.0' //生成RxHttp類

   //如下均爲非必須
   //管理協程生命週期,頁面銷燬,關閉請求
   implementation 'com.ljx.rxlife:rxlife-coroutine:2.0.0'  

   //Converter 根據本身需求選擇 RxHttp默認內置了GsonConverter
   implementation 'com.ljx.rxhttp:converter-jackson:2.2.0'
   implementation 'com.ljx.rxhttp:converter-fastjson:2.2.0'
   implementation 'com.ljx.rxhttp:converter-protobuf:2.2.0'
   implementation 'com.ljx.rxhttp:converter-simplexml:2.2.0'
}
複製代碼

注:純Java項目,請使用annotationProcessor替代kapt;依賴完,記得rebuild,纔會生成RxHttp類緩存

歡迎加入RxHttp&RxLife交流羣:378530627網絡

二、請求三部曲

相信還有以前沒了解過RxHttp的同窗,這裏貼出RxHttp請求流程圖,記住該圖,你就掌握了RxHttp的精髓,以下: 框架

代碼表示

val str = RxHttp.get("/service/...") //第一步,肯定請求方式,能夠選擇postForm、postJson等方法
    .toStr()    //第二步,確認返回類型,這裏表明返回String類型
    .await()    //第二步,使用await方法拿到返回值
複製代碼

怎麼樣,是否是很是簡單?異步

三、RxHttp操做符

3.一、retry 失敗重試

該操做符很是強大,不只作到了失敗重試,還作到了週期性失敗重試,即間隔幾秒後重試,來看下完整的方法簽名async

/** * 失敗重試,該方法僅在使用協程時纔有效 * @param times 重試次數, 默認Int.MAX_VALUE 表明不斷重試 * @param period 重試周期, 默認爲0, 單位: milliseconds * @param test 重試條件, 默認爲空,即無條件重試 */
fun retry( times: Int = Int.MAX_VALUE, period: Long = 0, test: ((Throwable) -> Boolean)? = null
)
複製代碼

retry()方法共有3個參數,分別是重試次數、重試周期、重試條件,都有默認值,3個參數能夠隨意搭配,如:函數

retry()    //無條件、不間斷、一直重試
retry(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、一直重試
複製代碼

前兩個參數相信你們一看就能明白,這裏對第3個參數額外說一下,經過第三個參數,咱們能夠拿到Throwable異常對象,咱們能夠對異常作判斷,若是須要重試,就返回true,不須要就返回false,下面看看具體代碼

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .retry(2, 1000) {       //重試2次,每次間隔1s 
        it is ConnectException   //若是是網絡異常就重試 
    }                                             
    .await()                     
複製代碼

3.二、timeout 超時

OkHttp提供了全局的讀、寫及鏈接超時,有時咱們也須要爲某個請求設置不一樣的超時時長,此時就能夠用到RxHttp的timeout(Long)方法,以下:

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(3000)      //超時時長爲3s 
    .await()                       
複製代碼

3.三、async 異步操做符

若是咱們由兩個請求須要並行時,就可使用該操做符,以下:

//同時獲取兩個學生信息
suspend void initData() {
  val asyncStudent1 = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .async()   //這裏會返回Deferred<Student> 
    
  val asyncStudent2 = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .async()   //這裏會返回Deferred<Student> 

  //隨後調用await方法獲取對象 
  val student1 = asyncStudent1.await()
  val student2 = asyncStudent2.await()
} 
複製代碼

3.四、delay、startDelay 延遲

delay操做符是請求結束後,延遲一段時間返回;而startDelay操做符則是延遲一段時間後再發送請求,以下:

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .delay(1000)      //請求回來後,延遲1s返回 
    .await()       
    
val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .startDelay(1000)     //延遲1s後再發送請求 
    .await()     
複製代碼

3.五、onErrorReturn、onErrorReturnItem異常默認值

有些狀況,咱們不但願請求出現異常時,直接走異常回調,此時咱們就能夠經過兩個操做符,給出默認的值,以下:

//根據異常給出默認值
val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(100)      //超時時長爲100毫秒 
    .onErrorReturn {
        //若是時超時異常,就給出默認值,不然,拋出原異常
        return@onErrorReturn if (it is TimeoutCancellationException)
            Student()                                              
        else                                                        
            throw it                                                
    }
    .await()
    
//只要出現異常,就返回默認值
val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(100)      //超時時長爲100毫秒 
    .onErrorReturnItem(Student())
    .await()
複製代碼

3.六、tryAwait 異常返回null

若是你不想在異常時返回默認值,又不想異常是影響程序的執行,tryAwait就派上用場了,它會在異常出現時,返回null,以下:

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(100)      //超時時長爲100毫秒 
    .tryAwait()     //這裏返回 Student? 對象,即有可能爲空 
複製代碼

3.七、map 轉換符號

map操做符很好理解,RxJava即協程的Flow都有該操做符,功能都是同樣,用於轉換對象,以下:

val student = RxHttp.postForm("/service/...")
    .toStr()
    .map { it.length }  //String轉Int 
    .tryAwait()     //這裏返回 Student? 對象,即有可能爲空 
複製代碼

3.八、以上操做符隨意搭配

以上操做符,可隨意搭配使用,但調用順序的不一樣,產生的效果也不同,這裏悄悄告訴你們,以上操做符只會對上游代碼產生影響。

timeout及retry

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(50)
    .retry(2, 1000) { it is TimeoutCancellationException }                                  
    .await()                       
複製代碼

以上代碼,只要出現超時,就會重試,而且最多重試兩次。

但若是timeoutretry互換下位置,就不同了,以下:

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .retry(2, 1000) { it is TimeoutCancellationException }       
    .timeout(50)                                  
    .await()                       
複製代碼

此時,若是50毫秒內請求沒有完成,就會觸發超時異常,而且直接走異常回調,不會重試。爲何會這樣?緣由很簡單,timeout及retry操做符,僅對上游代碼生效。如retry操做符,下游的異常是捕獲不到的,這就是爲何timeout在retry下,超時時,重試機制沒有觸發的緣由。

在看timeoutstartDelay操做符

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .startDelay(2000)       
    .timeout(1000)                                  
    .await()                       
複製代碼

以上代碼,一定會觸發超時異常,由於startDelay,延遲了2000毫秒,而超時時長只有1000毫秒,因此一定觸發超時。 但互換下位置,又不同了,以下:

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(1000)    
    .startDelay(2000)       
    .await()                       
複製代碼

以上代碼正常狀況下,都能正確拿到返回值,爲何?緣由很簡單,上面說過,操做符只會對上游產生影響,下游的startDelay延遲,它是無論的,也管不到。

四、協程開啓/關閉/異常處理

在以上示例中,咱們統一用到await/tryAwait操做符獲取請求返回值,它們都是suspend掛起函數,須要在另外一個suspend掛起函數或者協程中才能被調用,故咱們提供了RxLifeScope庫來處理協程開啓、關閉及異常處理,用法以下:

在FragemntActivity/Fragment/ViewModel環境下

在該環境下,直接調用rxLifeScope對象的lanuch方法開啓協程便可,以下:

rxLifeScope.lanuch({
    //協程代碼塊,運行在UI線程
    val student = RxHttp.postForm("/service/...")
    .toClass<Student>()                      
    .await()   
    //可直接更新UI
}, {
    //異常回調,這裏能夠拿到Throwable對象 
})
複製代碼

以上代碼,會在頁面銷燬時,自動關閉協程,同時自動關閉請求,無需擔憂內存泄露問題

非FragemntActivity/Fragment/ViewModel環境下

該環境下,咱們須要手動建立RxLifeScope對象,隨後調用lanuch方法開啓協程

val job = RxLifeScope().lanuch({
    //協程代碼塊,運行在UI線程
    val student = RxHttp.postForm("/service/...")
    .toClass<Student>()                      
    .await()   
    //可直接更新UI
}, {
    //異常回調,這裏能夠拿到Throwable對象 
})

//在合適的時機關閉協程
job.cancel()
複製代碼

以上代碼,因爲未與生命週期綁定,故咱們須要在合適的時機,手動關閉協程,協程關閉,請求也會跟着關閉

監聽協程開啓/結束回調

以上咱們在lanuch方法,傳入協程運行回調及異常回調,咱們也能夠傳入協程開啓及結束回調,以下:

rxLifeScope.launch({                                      
    //協程代碼塊 
    val student = RxHttp.postForm("/service/...")
        .toClass<Student>()
        .await()   
    //可直接更新UI 
}, {                                                      
    //異常回調,這裏能夠拿到Throwable對象,運行在UI線程 
}, {                                                     
    //開始回調,能夠開啓等待彈窗,運行在UI線程 
}, {                                                     
    //結束回調,能夠銷燬等待彈窗,運行在UI線程 
})                                                       
複製代碼

以上回調,均運行在UI線程

五、小結

能夠看到,前面文章開頭提到超時/重試問題,就用timeout/retry,延遲就用delay/startDelay,出現異常不想中斷協程的運行,就用onErrorReturn/onErrorReturnItem或者tryAwait,總之,一切都是那麼的優雅。

RxHttp的優雅遠不止這些,BaseUrl的處理,文件上傳/下載/進度監聽,緩存處理、業務code統一判斷等等,處理的都使人歎爲觀止,

更多功能查看如下文章

協程用法:RxHttp ,比Retrofit 更優雅的協程體驗

RxJava用法:RxHttp 讓你眼前一亮的Http請求框架

RxHttp 全網Http緩存最優解

最後,開源不易,寫文章更不易,若是你以爲不錯RxHttp或給你帶來了幫助,歡迎點贊收藏,以備不時之需,若是能夠,再給個star,我將感激涕零,🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏

相關文章
相關標籤/搜索