美文网首页KotlinKotlin禅与计算机程序设计艺术
RxKotlin 例子不超过15行教程 7----Backpr

RxKotlin 例子不超过15行教程 7----Backpr

作者: 蔡洪宇 | 来源:发表于2018-02-27 22:50 被阅读199次

    依照惯例, 先放两个能跑的代码
    !!本节中所有 Item 均为这段代码!!

    // item.kt
    data class Item(val id: Int) {
        init {  // 锚点1
            println("Item Created $id")
        }
    }
    

    能跑的代码

    // 7.1.kt
    import io.reactivex.Observable
    
    fun main(args: Array<String>) {
        Observable.just(1, 2, 3).map { Item(it) }  // 注释1
                .subscribe({
                    println("Received $it")
                    Thread.sleep(100)
                })
        Thread.sleep(1000)
    }
    

    输出

    Item Created 1
    Received Item(id=1)
    Item Created 2
    Received Item(id=2)
    Item Created 3
    Received Item(id=3)
    

    注释1
    这里 Map 的意义就是利用对象初始化(锚点1)时能打印出来一个值这个特性, 便于我们看 一个值被弹出的时间点 (用来和 这个值被处理的时间点 作比较), 把数字转不转化成对象在这里没有特殊含义(是副产品)

    上面代码主旨大意

    从输出中可以看到基本流程是:
    一个值被 Observable 弹出 -> 被 Observer 处理 -> 下一个值被弹出 -> ...
    这是因为 ObservableObserver 运行在一个线程中, 所以在 Observer 没处理完上一个值之前 Observable 是不能弹出下一个值的。那如果 ObservableObserver 运行在不同的线程中呢?来看下一个例子

    // 7.2.kt
    import io.reactivex.Observable
    import io.reactivex.schedulers.Schedulers
    
    fun main(args: Array<String>) {
        Observable.just(1, 2, 3).map { Item(it) }
                .observeOn(Schedulers.newThread())  // 注释1  Scheduler 会在之后的章节说明
                .subscribe({
                    Thread.sleep(100)
                    println("Received $it")
                })
        Thread.sleep(1000)
    }
    

    输出

    Item Created 1
    Item Created 2
    Item Created 3
    Received Item(id=1)
    Received Item(id=2)
    Received Item(id=3)
    

    注释1
    这里只需要知道这一行代码使得 Observer 在另一个线程中运行即可

    上面代码主旨大意

    从输出中可以看到 Observable 完全不会顾及 Observer 的感受, 只会一个值接一个值的弹。如果此时 Observer 执行某些耗时计算(即 值被 Observable 弹出的速度远远快于 被 Observer 处理的速度)(这里耗时计算我们用 sleep 模拟), 那么这些值在未被处理之前都会累积在内存中,如果数据量很大甚至可以导致内存溢出(OutOfMemory)。

    如果数据的 消费者(在这里是 Observer) 可以通过某种渠道告知数据的 生产者(在这里是 Observable) "先别弹出值了, 等我把累积的值处理完你再接着弹" 这一信息呢?

    这个反馈渠道是存在的, 在 ReactiveX 中, 我们叫它 Backpressure (背压)

    Backpressure

    背压 , 指的是后端的压力, 通常用于描述系统排出的流体在出口处或二次侧受到的与流动方向相反的压力。----百度百科

    ObservableObserver 是不支持 Backpressure 的。替代方案是 FlowableSubscriber

    Backpressure 生产者 消费者
    不支持 Observable Observer
    支持 Flowable Subscriber

    FlowableReactiveX 2.x (RxKotlin 2.X) 中被加入, 之前的版本不包括它。

    Flowable

    // 7.3.kt
    import io.reactivex.Flowable  // 和 7.2.kt 相比, 这一行有改变
    import io.reactivex.schedulers.Schedulers
    
    fun main(args: Array<String>) {
        Flowable.just(1, 2, 3).map { Item(it) }  // 和 7.2.kt 相比, 这一行有改变
                .observeOn(Schedulers.newThread())
                .subscribe({  // 注释1
                    Thread.sleep(100)
                    println("Received $it")
                })
        Thread.sleep(1000)
    }
    

    输出

    Item Created 1
    Item Created 2
    Item Created 3
    Received Item(id=1)
    Received Item(id=2)
    Received Item(id=3)
    

    注释1
    这里是 Subscriber 而不是 Observer, 但是由于用的 Lambda 形式, 看起来一样。我们会在之后讨论。
    上面的输出和 7.2.kt 的输出完全一致, 我一会说为什么没有效果
    看下一个例子

    // 7.4.kt
    import io.reactivex.Flowable
    import io.reactivex.schedulers.Schedulers
    
    fun main(args: Array<String>) {
        Flowable.range(1, 260).map { Item(it) }  // 和 7.3.kt 相比, 只有这一行有改变
                .observeOn(Schedulers.newThread())
                .subscribe({
                    Thread.sleep(100)
                    println("Received $it")
                })
        Thread.sleep(27000)
    }
    

    输出(原谅我吧, 数据量太小根本没有效果)

    Item Created 1
    Item Created 2
    ...
    Item Created 127
    Item Created 128  // 当 Flowable 弹出 128 个值就暂时停止了, 缓冲区达到上限(128)
    Received Item(id=1)
    Received Item(id=2)
    ...
    Received Item(id=95)
    Received Item(id=96)  // Subscriber 仅仅处理了 96 个值, 缓冲区没有被清空
    Item Created 129
    Item Created 130
    ...
    Item Created 223
    Item Created 224  // 缓冲区再次达到上限 224-96=128
    Received Item(id=97)
    Received Item(id=98)
    ...
    Received Item(id=191)
    Received Item(id=192)
    Item Created 225
    Item Created 226
    ...
    Item Created 259
    Item Created 260  // 这次是因为没有值了
    Received Item(id=193)
    Received Item(id=194)
    ...
    Received Item(id=259)
    Received Item(id=260)
    

    Flowable 不会一下子把所有值全部弹出, 它会一块一块的弹, 当 Subscriber 跟上时才会继续
    Flowable 会维护一个默认大小为 128 个元素的缓冲区, 被弹出的元素会暂存其中。如果满了 Flowable 就会暂时停止弹射。

    7.3.kt 数据量还没有达到缓冲区上限, 所以看起来没有效果。
    7.3.kt 这个例子, 还有其他原因, 之后再说。

    Subscriber

    Subscriber 其实可以 动态 限定从上游拿到的值的数量(动态 这个词接下来就会解释,不要急), 但是我们之前一直用的 Lambda 形式, 系统默认从上游获取所有值。
    我们来看一看如何限定这个数量。
    先来看一个 Subscriber(有点长,并且本节其它的 Subscriber 都这么长, 非常抱歉)

    // subscriber_1.kt
    import org.reactivestreams.Subscriber
    import org.reactivestreams.Subscription
    
    val subscriber_1 = object : Subscriber<Item> {
        override fun onSubscribe(subscription: Subscription) {
            subscription.request(4)  // 注释1  限定请求 4 个值
            println("New Subscription ")
        }
    
        override fun onNext(s: Item) {
            Thread.sleep(200)
            println("Subscriber received " + s)
        }
    
        override fun onError(e: Throwable) {
            e.printStackTrace()
        }
    
        override fun onComplete() {
            println("Done!")
        }
    }
    

    可以通过下面来调用上面的 subscriber_1

    // 7.5.kt
    import io.reactivex.Flowable
    import io.reactivex.schedulers.Schedulers
    
    fun main(args: Array<String>) {
        Flowable.range(1, 6)
                .map { Item(it) }
                .observeOn(Schedulers.newThread())
                .subscribe(subscriber_1)  // 注释2
        Thread.sleep(2000)
    }
    

    输出

    New Subscription
    Item Created 1
    Item Created 2
    Item Created 3
    Item Created 4
    Item Created 5
    Item Created 6
    Subscriber received Item(id=1)
    Subscriber received Item(id=2)
    Subscriber received Item(id=3)
    Subscriber received Item(id=4)  // 注释3
    

    注释1
    如果删掉这一行, 我们就没有限定请求数量, 一个值都接收不到(我原本以为会接收到所有值的, 做了实验才发现和我想的不太一样)
    注释2
    我没有在接下来都使用一个 Subscriber 来避免重复代码
    但是我会对代码进行处理,把和上一个不一样的地方用注释标识出来
    因为 Subscriber 的重点在通过不同的配置来控制流速和总量。
    为了灵活, 我选择创建多个 Subscriber
    注释3
    我们只请求 4 个值, 数据流并没有到结尾, 系统没有调用 onComplete 方法。所以没有输出 Done!
    我再举一个例子, 这个例子只是想说明何时 onComplete 会被调用

    // 7.6.kt
    import io.reactivex.Flowable
    import io.reactivex.schedulers.Schedulers
    
    fun main(args: Array<String>) {
        Flowable.range(1, 4)  // 只有这一行与 7.5.kt 相比有变动
                .map { Item(it) }
                .observeOn(Schedulers.newThread())
                .subscribe(subscriber_1)
        Thread.sleep(2000)
    }
    

    输出

    New Subscription
    Item Created 1
    Item Created 2
    Item Created 3
    Item Created 4
    Subscriber received Item(id=1)
    Subscriber received Item(id=2)
    Subscriber received Item(id=3)
    Subscriber received Item(id=4)
    Done!  // onComplete 被调用了
    

    当且仅当数据流真正到达末尾时才会触发 onComplete。限定数量是不会的

    之前我说 Subscriber 可以 动态 限定从上游拿到的值的数量, 这个动态可以从下面体现

    // subscriber_2.kt
    import org.reactivestreams.Subscriber
    import org.reactivestreams.Subscription
    
    val subscriber_2 = object : Subscriber<Item> {
        lateinit var subscription: Subscription  // 与 subscriber_1 相比多了这一行
        override fun onSubscribe(subscription: Subscription) {
            this.subscription = subscription  // 与 subscriber_1 相比多了这一行
            subscription.request(4)
            println("New Subscription ")
        }
    
        override fun onNext(s: Item) {
            Thread.sleep(200)
            println("Subscriber received " + s)
            if (s.id == 4) {                    // |\
                println("Requesting two more")  // | \
                subscription.request(2)         // | /--- 与 subscriber_1 相比多了这几行
            }                                   // |/
        }
    
        override fun onError(e: Throwable) {
            e.printStackTrace()
        }
    
        override fun onComplete() {
            println("Done!")
        }
    }
    
    // 7.7.kt
    import io.reactivex.Flowable
    import io.reactivex.schedulers.Schedulers
    
    fun main(args: Array<String>) {
        Flowable.range(1, 6)
                .map { Item(it) }
                .observeOn(Schedulers.newThread())
                .subscribe(subscriber_2) // 只有这一行与 7.5.kt 相比有变动
        Thread.sleep(2000)
    }
    

    输出

    New Subscription
    Item Created 1
    Item Created 2
    Item Created 3
    Item Created 4
    Item Created 5
    Item Created 6
    Subscriber received Item(id=1)
    Subscriber received Item(id=2)
    Subscriber received Item(id=3)
    Subscriber received Item(id=4)
    Requesting two more  // 决定再从上游拿 2 个值进行处理
    Subscriber received Item(id=5)
    Subscriber received Item(id=6)
    Done!
    

    OK 这一节就到这里。下一节我们说一说 Flowable 把速度降下来的几种方式~

    RxKotlin 例子不超过15行教程 1----环境配置与初体验

    RxKotlin 例子不超过15行教程 2----Observable Observer 与 Subscribe 简介

    RxKotlin 例子不超过15行教程 3----Observable 的创建

    RxKotlin 例子不超过15行教程 4----Observer Subscribe 与 Hot/Cold Observable

    RxKotlin 例子不超过15行教程 5----Subject

    RxKotlin 例子不超过15行教程 6----Operator 与 Marble Diagram

    RxKotlin 例子不超过15行教程 7----Backpressure Flowable 与 Subscriber 简介

    RxKotlin 例子不超过15行教程 8----Error Handling

    相关文章

      网友评论

        本文标题:RxKotlin 例子不超过15行教程 7----Backpr

        本文链接:https://www.haomeiwen.com/subject/kbamxftx.html