可怕!RxHttp2.0重大更新!协程发请求,仅需三步

1、前言

RxHttp 在v2.0版本中加入对协程的支持,收到了广大kotlin用户的喜爱,他们也不禁感慨,原来协程发请求还能如此优雅,比retrofit强大的不止一点点,然而,这就够了吗?远远不够,为啥,因为还有痛点没解决,为此,我也收集几个目前网络请求遇到的痛点,如下:

  • 异步操作,协程已为我们提供了async操作符处理异步问题,但用到时,每次还要包装一次,不能接受
  • 超时与重试,这种情况遇到的不多,但几乎每个开发者都会遇到,真遇到时,如果没有对应的API,也着实让人着急
  • 请求开始/结束延迟,这种情况也不多,但遇到的人也不少,自己处理着实麻烦
  • 在请求并行中,假设有A、B两个请求甚至更多,它们互不依赖,然而在协程中,如果A请求出现异常,那么协程就中止了,此时B也跟着中止了,这是我们不想看到的结果,如何解决?常规的做法是对每个请求都做异常处理,使得出现异常,协程不会结束。但每个请求都需要单独处理,写起来着实会让人抓破头皮,这是很大的痛点

等等,其实还有很多小细节的问题,这里就就不一一列举了。

正因有以上问题,所以RxHttp v2.2.0版本就来了,该版本主要改动如下

  • 新增一系列非常好用的操作符,如:asysntimeoutretrytryAwait等等
  • 完全剔除RxJava,采用外挂方法替代,也正因如此,RxHttp做到同时支持RxJava2与RxJava3
  • RxLieScope提取为单独的一个库,专门处理协程开启/关闭/异常处理,本文后续会单独介绍

gradle依赖

dependencies {
   //必须
   implementation 'com.ljx.rxhttp:rxhttp:2.2.1'
   kapt 'com.ljx.rxhttp:rxhttp-compiler:2.2.1' //生成RxHttp类

   //以下均为非必须
   //管理协程生命周期,页面销毁,关闭请求
   implementation 'com.ljx.rxlife:rxlife-coroutine:2.0.0'  

   //Converter 根据自己需求选择 RxHttp默认内置了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项目,请使用annotationProcessor替代kapt;依赖完,记得rebuild,才会生成RxHttp类

欢迎加入RxHttp&RxLife交流群:378530627

2、请求三部曲

相信还有没了解过RxHttp的同学,这里贴出RxHttp请求流程图,记住该图,你就掌握了RxHttp的精髓,如下:


image

代码表示

val str = RxHttp.get("/service/...") //第一步,确定请求方式,可以选择postForm、postJson等方法
    .toStr()    //第二步,确认返回类型,这里代表返回String类型
    .await()    //第二步,使用await方法拿到返回值

怎么样,是不是非常简单?

3、RxHttp操作符

3.1、retry 失败重试

该操作符非常强大,不仅做到了失败重试,还做到了周期性失败重试,即间隔几秒后重试,来看下完整的方法签名

/**
 * 失败重试,该方法仅在使用协程时才有效
 * @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)   //无条件、不间断、重试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.2、timeout 超时

OkHttp提供了全局的读、写及连接超时,有时我们也需要为某个请求设置不同的超时时长,此时就可以用到RxHttp的timeout(Long)方法,如下:

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(3000)      //超时时长为3s                           
    .await()                       

3.3、async 异步操作符

如果我们有两个请求需要并行时,就可以使用该操作符,如下:

//同时获取两个学生信息
suspend void initData() {
  val asyncStudent1 = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .async(this)   //this为CoroutineScope对象,这里会返回Deferred<Student>   
    
  val asyncStudent2 = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .async(this)   //this为CoroutineScope对象,这里会返回Deferred<Student>   

  //随后调用await方法获取对象    
  val student1 = asyncStudent1.await()
  val student2 = asyncStudent2.await()
} 

3.4、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.5、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.6、tryAwait 异常返回null

如果你不想在异常时返回默认值,又不想异常是影响程序的执行,tryAwait就派上用场了,它会在异常出现时,返回null,如下:

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(100)      //超时时长为100毫秒                        
    .tryAwait()     //这里返回 Student? 对象,即有可能为空  

3.7、map 转换符号

map操作符很好理解,RxJava即协程的Flow都有该操作符,功能都是一样,用于转换对象,如下:

val student = RxHttp.postForm("/service/...")
    .toStr()
    .map { it.length }  //String转Int                        
    .tryAwait()     //这里返回 Student? 对象,即有可能为空  

3.8、以上操作符随意搭配

以上操作符,可随意搭配使用,但调用顺序的不同,产生的效果也不一样,这里悄悄告诉大家,以上操作符只会对上游代码产生影响。

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延迟,它是不管的,也管不到。

4、协程开启/关闭/异常处理

在以上示例中,我们统一用到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线程

5、小结

可以看到,前面文章开头提到超时/重试问题,就用timeout/retry,延迟就用delay/startDelay,出现异常不想中断协程的运行,就用onErrorReturn/onErrorReturnItem或者tryAwait,总之,一切都是那么的优雅。

RxHttp的优雅远不止这些,BaseUrl的处理,文件上传/下载/进度监听,缓存处理、业务code统一判断等等,处理的都令人叹为观止,

更多功能查看以下文章

协程用法:RxHttp ,比Retrofit 更优雅的协程体验

RxJava用法:RxHttp 让你眼前一亮的Http请求框架

RxHttp 全网Http缓存最优解

最后,开源不易,写文章更不易,如果你觉得不错RxHttp或给你带来了帮助,欢迎点赞收藏,以备不时之需,如果可以,再给个star,我将感激不尽,🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342