RxHttp 2000+star,协程请求,android项目开发实战

sort 排序

排序有sortXxx、sortedXxx两大类型操作符,区别在于sortXxx在列表内排序,排序完,返回自身,而sortedXxx在列表外排序,排序完,返回新的列表,这里只对sortXxx介绍,如下:

//根据id顺序排序
val students = RxHttp.postForm("/service/…")
.toList()
.sortBy { it.id }
.await()

//根据id、age两个字段顺序排序,id优先,其次age
val students = RxHttp.postForm("/service/…")
.toList()
.sortBy({ it.id }, { it.age })
.await()

//返回两个排序对象,自行实现排序规则
val students = RxHttp.postForm("/service/…")
.toList()
.sortWith { student1, student2 ->
student1.id.compareTo(student2.id)
}
.await()

flowOn 指定上游所在线程

该操作符跟Flow里面的flowOn操作符一样,用于指定上游所在线程,如下:

val students = RxHttp.postForm("/service/…")
.toList()
.sortBy { it.id } //IO线程执行
.flowOn(Dispatchers.IO)
.distinctBy { it.id } //Default线程执行
.flowOn(Dispatchers.Default)
.filter{ it.age > 20 } //IO线程执行
.flowOn(Dispatchers.IO)
.flowOn(Dispatchers.Default)
.await()

asFlow 转Flow对象

如果你喜欢kotlin的flow流,那么asFlow 就派上用场了,如下:

RxHttp.postForm("/service/…")
.toList()
.asFlow()
.collect {
//这里拿到List对象
}

注意:使用asFlow操作符后,需要使用collect替代await操作符

subList、take 截取列表

subList用于截取某段列表,截取范围越界,则抛出越界异常;take用于从0开始,取n个数据,不足n个时,返回全部,如下:

val students = RxHttp.postForm("/service/…")
.toList()
.subList(1,10) //截取9个数据
.take(5) //从9个中取前5个
.await()

async 异步操作

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

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

val asyncStudent2 = RxHttp.postForm("/service/…")
.toResponse()
.async(this) //this为CoroutineScope对象,这里会返回Deferred

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

delay、startDelay 延迟

delay操作符是请求结束后,延迟一段时间返回;而startDelay操作符则是延迟一段时间后再发送请求,如下:

val student = RxHttp.postForm("/service/…")
.toResponse()
.delay(1000) //请求回来后,延迟1s返回
.await()

val student = RxHttp.postForm("/service/…")
.toResponse()
.startDelay(1000) //延迟1s后再发送请求
.await()

onErrorReturn、onErrorReturnItem异常默认值

有些情况,我们不希望请求出现异常时,直接走异常回调,此时我们就可以通过两个操作符,给出默认的值,如下:

//根据异常给出默认值
val student = RxHttp.postForm("/service/…")
.toResponse()
.timeout(100) //超时时长为100毫秒
.onErrorReturn {
//如果时超时异常,就给出默认值,否则,抛出原异常
return@onErrorReturn if (it is TimeoutCancellationException)
Student()
else
throw it
}
.await()

//只要出现异常,就返回默认值
val student = RxHttp.postForm("/service/…")
.toResponse()
.timeout(100) //超时时长为100毫秒
.onErrorReturnItem(Student())
.await()

awaitResult 返回kotlin.Result

这个就直接看代码吧

val result: Result = RxHttp
.postForm("/service/…")
.toClass()
.awaitResult()
if (result.isSuccess) {
//请求成功,拿到Student对象
val student = result.getOrThrow()
} else {
//请求出现异常,拿到Throwable对象
val throwable = result.exceptionOrNull()
}

拿到kotlin.Result对象后,我们需要判断请求成功与否,随后在执行相关逻辑

tryAwait 异常返回null

tryAwait会在异常出现时,返回null,如下:

val student = RxHttp.postForm("/service/…")
.toResponse()
.timeout(100) //超时时长为100毫秒
.tryAwait() //这里返回 Student? 对象,如果出现异常,那它就是null

自定义操作符

RxHttp内置了一系列强大又好用的操作符,然而肯定满足不了所有的业务场景,此时我们就可以考虑自定义操作符

自定义takeLast操作符

如我们有这样一个需求,自定义需要在列表尾部取n条数据,不足n条,返回全部

前面我们介绍了take操作符,它是从0开始,取n条数据,如果不足n条,则全部返回,来看看源码

fun IAwait<out Iterable>.take(
count: Int
): IAwait<List> = newAwait {
await().take(count)
}

代码解读,

1、IAwait是一个接口,如下:

interface IAwait {

suspend fun await(): T
}

该接口仅有一个await()方法,返回声明的T

2、newAwait操作符,只是创建了一个IAwait接口的实现而已,如下:

inline fun <T, R> IAwait.newAwait(
crossinline block: suspend IAwait.() -> R
): IAwait = object : IAwait {

override suspend fun await(): R {
return this@newAwait.block()
}
}

3、由于我们是为IAwait<out Iterable<T>>对象扩展的take方法,故在内部,我们调用await()方法它返回Iterable<T>对象,最后就执行Iterable<T>对象的扩展方法take(Int)获取从0开是的n条数据,take(Int)是系统提供的方法,源码如下:

public fun Iterable.take(n: Int): List {
require(n >= 0) { “Requested element count $n is less than zero.” }
if (n == 0) return emptyList()
if (this is Collection) {
if (n >= size) return toList()
if (n == 1) return listOf(first())
}
var count = 0
val list = ArrayList(n)
for (item in this) {
list.add(item)
if (++count == n)
break
}
return list.optimizeReadOnlyList()
}

ok,回到前面的话题,如何自定义一个操作,实现在列表尾部取n条数据,不足n条,返回全部

看了上面的take(int)源码,我们就可以很轻松的写出如下代码:

fun IAwait<out List>.takeLast(
count: Int
): IAwait<List> = newAwait {
await().takeLast(count)
}

首先,我们对IAwait<out List<T>>扩展了takeLast(Int)方法,随后调用newAwait创建了IAwait接口的实例对象,接着调用await()方法返回List<T>对象,最后调用系统为List<T>扩展的takeLast(Int)方法

定义好了,我们就可直接使用,如下:

val students = RxHttp.postForm("/service/…")
.toList()
.takeLast(5) //取列表尾部5条数据,不足时,全部返回
.await()

以上操作符随意搭配

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

timeout及retry

val student = RxHttp.postForm("/service/…")
.toResponse()
.timeout(50)
.retry(2, 1000) { it is TimeoutCancellationException }
.await()

以上代码,只要出现超时,就会重试,并且最多重试两次。

但如果timeoutretry互换下位置,就不一样了,如下:

val student = RxHttp.postForm("/service/…")
.toResponse()
.retry(2, 1000) { it is TimeoutCancellationException }
.timeout(50)
.await()

此时,如果50毫秒内请求没有完成,就会触发超时异常,并且直接走异常回调,不会重试。为什么会这样?原因很简单,timeout及retry操作符,仅对上游代码生效。如retry操作符,下游的异常是捕获不到的,这就是为什么timeout在retry下,超时时,重试机制没有触发的原因。

在看timeoutstartDelay操作符

val student = RxHttp.postForm("/service/…")
.toResponse()
.startDelay(2000)
.timeout(1000)
.await()

以上代码,必定会触发超时异常,因为startDelay,延迟了2000毫秒,而超时时长只有1000毫秒,所以必定触发超时。 但互换下位置,又不一样了,如下:

val student = RxHttp.postForm("/service/…")
.toResponse()
.timeout(1000)
.startDelay(2000)
.await()

以上代码正常情况下,都能正确拿到返回值,为什么?原因很简单,上面说过,操作符只会对上游产生影响,下游的startDelay延迟,它是不管的,也管不到。

3、上传/下载

RxHttp对文件的优雅操作是与生俱来的,在协程的环境下,依然如此,没有什么比代码更具有说服力,直接上代码

3.1、文件上传

val result = RxHttp.postForm("/service/…")
.addFile(“file”, File(“xxx/1.png”)) //添加单个文件
.addFile(“fileList”, ArrayList()) //添加多个文件
.toResponse()
.await()

只需要通过addFile系列方法添加File对象即可,就是这么简单粗暴,想监听上传进度?简单,再加一个upload操作符即可,如下:

val result = RxHttp.postForm("/service/…")
.addFile(“file”, File(“xxx/1.png”))
.addFile(“fileList”, ArrayList())
.upload(this) { //此this为CoroutineScope对象,即当前协程对象
//it为Progress对象
val process = it.progress //已上传进度 0-100
val currentSize = it.currentSize //已上传size,单位:byte
val totalSize = it.totalSize //要上传的总size 单位:byte
}
.toResponse()
.await()

我们来看下upload方法的完整签名,如下:

/**

  • 调用此方法监听上传进度
  • @param coroutine CoroutineScope对象,用于开启协程,回调进度,进度回调所在线程取决于协程所在线程
  • @param progress 进度回调
  • 注意:此方法仅在协程环境下才生效
    */
    fun RxHttpFormParam.upload(
    coroutine: CoroutineScope? = null,
    progress: (Progress) -> Unit
    ):RxHttpFormParam

3.2、文件下载

接着再来看看下载,直接贴代码

val localPath = “sdcard//android/data/…/1.apk”
val student = RxHttp.get("/service/…")
.toDownload(localPath) //下载需要传入本地文件路径
.await()

下载调用toDownload(String)方法,传入本地文件路径即可,要监听下载进度?也简单,如下:

val localPath = “sdcard//android/data/…/1.apk”
val student = RxHttp.get("/service/…")
.toDownload(localPath, this) { //此this为CoroutineScope对象
//it为Progress对象
val process = it.progress //已下载进度 0-100
val currentSize = it.currentSize //已下载size,单位:byte
val totalSize = it.totalSize //要下载的总size 单位:byte
}
.await()

看下toDownload方法完整签名

/**

  • @param destPath 本地存储路径
  • @param coroutine CoroutineScope对象,用于开启协程,回调进度,进度回调所在线程取决于协程所在线程
  • @param progress 进度回调
    */
    fun IRxHttp.toDownload(
    destPath: String,
    coroutine: CoroutineScope? = null,
    progress: (Progress) -> Unit
    ): IAwait

如果你需要断点下载,也是可以的,一行代码的事,如下:

val localPath = “sdcard//android/data/…/1.apk”
val student = RxHttp.get("/service/…")
.setRangeHeader(1000, 300000) //断点下载,设置下载起始/结束位置
.toDownload(localPath, this) { //此this为CoroutineScope对象
//it为Progress对象
val process = it.progress //已下载进度 0-100
val currentSize = it.currentSize //已下size,单位:byte
val totalSize = it.totalSize //要下的总size 单位:byte
}
.await()

老规则,看下setRangeHeader完整签名

/**

  • 设置断点下载开始/结束位置
  • @param startIndex 断点下载开始位置
  • @param endIndex 断点下载结束位置,默认为-1,即默认结束位置为文件末尾
  • @param connectLastProgress 是否衔接上次的下载进度,该参数仅在带进度断点下载时生效
    */
    fun setRangeHeader (
    startIndex: Long,
    endIndex: Long = 0L,
    connectLastProgress: Boolean = false
    )

到这,RxHttp协程的基础Api基本介绍完毕,那么问题了,以上介绍的Api都依赖与协程环境,那我这么开启协程呢?亦或者说,我对协程不是很懂,你只要保证安全的前提下,告诉怎么用就行了,ok,那下面如何安全的开启一个协程,做到自动异常捕获,且页面销毁时,自动关闭协程及请求

4、协程开启及关闭

此时就要引入本人开源的另一个库RxLife-Coroutine,用于开启/关闭协程,并自动异常捕获,依赖如下:

implementation ‘com.ljx.rxlife:rxlife-coroutine:2.0.0’

本文在介绍业务code统一处理的时候,我们用到rxLifeScope属性开启协程,那这个是什么类型呢?看代码

val ViewModel.rxLifeScope: RxLifeScope
get() {
val scope: RxLifeScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(JOB_KEY, RxLifeScope())
}

val LifecycleOwner.rxLifeScope: RxLifeScope
get() = lifecycle.rxLifeScope

可以看到,我们为ViewModelLifecycleOwner都扩展了一个名为rxLifeScope的属性,类型为RxLifeScope,ViewModel相信大家都知道了,这里就简单讲一下LifecycleOwner接口,我们的Fragment及FragmentActivity都实现了LifecycleOwner接口,而我们的Activity一般继承于AppCompatActivity,而AppCompatActivity继承于FragmentActivity,所以我们在FragmentActivity/Fragment/ViewModel环境下,可以直接使用rxLifeScope开启协程,如下:

rxLifeScope.lanuch({
//协程代码块,运行在UI线程
}, {
//异常回调,协程代码块出现任何异常,都会直接走这里
})

通过这种方式开启的协程,会在页面销毁时,会自动关闭协程,当然,如果你的协程代码块里还有RxHttp请求的代码,协程关闭的同时,也是关闭请求,所以在这种情况下,只需要知道如何开启协程就行,其它一律不管。

现在,我们来看下rxLifeScope.lanuch方法的完整签名

/**

  • @param block 协程代码块,运行在UI线程
  • @param onError 异常回调,运行在UI线程
  • @param onStart 协程开始回调,运行在UI线程
  • @param onFinally 协程结束回调,不管成功/失败,都会回调,运行在UI线程
    */
    fun launch(
    block: suspend CoroutineScope.() -> Unit,
    onError: ((Throwable) -> Unit)? = null,
    onStart: (() -> Unit)? = null,
    onFinally: (() -> Unit)? = null
    ): Job

可以看到,不仅有失败回调,还有开始及结束回调,这对于我们发请求来说,真的非常方便,如下:

rxLifeScope.launch({
//协程代码块
val students = RxHttp.postJson("/service/…")
.toResponse<List>()
.await()
//可以直接更新UI
}, {
//异常回调,这里可以拿到Throwable对象
}, {
//开始回调,可以开启等待弹窗
}, {
//结束回调,可以销毁等待弹窗
})

以上代码均运行在UI线程中,请求回来后,便可直接更新UI

也许你还有疑问,我在非FragmentActivity/Fragment/ViewModel环境下,如何开启协程,又如何关闭,其实也很简单,如下:

val job = RxLifeScope().launch({
val students = RxHttp.postJson("/service/…")
.toResponse<List>()
.await()
}, {
//异常回调,这里可以拿到Throwable对象
}, {
//开始回调,可以开启等待弹窗
}, {
//结束回调,可以销毁等待弹窗
})
job.cancel() //关闭协程

以上代码,需要注意两点,第一,我们需要手动创建RxLifeScope()对象,随后开启协程;第二,开启协程后,可以拿到Job对象,我们需要通过该对象手动关闭协程。其它就没啥区别了。

5、协程多任务处理

最后

希望本文对你有所启发,有任何面试上的建议也欢迎留言分享给大家。

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以加一下下面的技术群来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

这里放一下资料获取方式:GitHub

好了~如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以加一下下面的技术群来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

这里放一下资料获取方式:GitHub

[外链图片转存中…(img-oG5Nukme-1645110129191)]

好了~如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

[外链图片转存中…(img-XRrYDO7S-1645110129192)]

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值