RxJava学习 - 9. Multicasting, Replaying, and Caching

我们已经见过hot和cold Observable,虽然大部分是cold(甚至使用Observable.interval()的)。当你有不止一个Observer的时候,默认行为是为每个Observer增加一个分开的流。你也许希望这样,也许不希望这样,我们需要意识到:什么时候,通过multicasting(使用ConnectableObservable)会把一个Observable强制变成hot。

Understanding multicasting

看下面的代码:

import io.reactivex.Observable;

public class Launcher {
    public static void main(String[] args) {
        Observable<Integer> threeIntegers = Observable.range(1, 3);

        threeIntegers.subscribe(i -> System.out.println("Observer One: " + i));
        threeIntegers.subscribe(i -> System.out.println("Observer Two: " + i));
    }
}

输出是

Observer One: 1
Observer One: 2
Observer One: 3
Observer Two: 1
Observer Two: 2
Observer Two: 3

上面的程序,第一个Observer接收完所有的三个emissions,然后调用onComplete()。然后,第二个Observer接收三个emissions(重新生成的),然后调用onComplete()。这是两个分开的流。如果我们想把他们合并到一个流,把每个emission都发给Observers,可以调用Observable的publish(),这样返回一个ConnectableObservable。设置Observers,调用connect()来开始发射,两个Observers收到了相同的emissions:

import io.reactivex.Observable;
import io.reactivex.observables.ConnectableObservable;

public class Launcher {
    public static void main(String[] args) {
        ConnectableObservable<Integer> threeIntegers =
                Observable.range(1, 3).publish();
        threeIntegers.subscribe(i -> System.out.println("Observer One: " + i));
        threeIntegers.subscribe(i -> System.out.println("Observer Two: " + i));
        threeIntegers.connect();
    }
}

输出是

Observer One: 1
Observer Two: 1
Observer One: 2
Observer Two: 2
Observer One: 3
Observer Two: 3

使用ConnectableObservable会强制源成为hot,发射一个流给所有的Observers。这就叫multicasting,但是调用不同的operators,还有细微的差别。
甚至当你调用publish(),使用ConnectableObservable,下来的任何operators还是可以增加分开的流。我们看看这行为,该如何管理。

Multicasting with operators

看看multicasting在一个operators链内是如何工作的,我们使用Observable.range(),然后map每个emission成一个随机整数。因为这些随机值对每个订阅来说,是不确定的,也是不同的,可以让我们观察multicasting是否工作了。
我们发射数字1-3,映射成一个0-100000的随机整数。如果有两个Observers,我们可以希望每个收到不同的值。注意,你的输出和我的输出是不同的,我们感兴趣的是,确认两个Observers接收到不同的随机数:

import io.reactivex.Observable;
import java.util.concurrent.ThreadLocalRandom;

public class Launcher {
    public static void main(String[] args) {
        Observable<Integer> threeRandoms = Observable.range(1,3).map(i -> randomInt());

        threeRandoms.subscribe(i -> System.out.println("Observer 1: " + i));
        threeRandoms.subscribe(i -> System.out.println("Observer 2: " + i));        
    }
    
    public static int randomInt() {
        return ThreadLocalRandom.current().nextInt(100000);
    }        
}

我的输出是

Observer 1: 6207
Observer 1: 10604
Observer 1: 5121
Observer 2: 7127
Observer 2: 94588
Observer 2: 34253

看,Observable.range()源产生了两个分开的emission生成器,每个都是cold的。每个流有自己的分开的map()实例,
于是,每个Observer得到了不同的随机数。流的结构如下:
Two separate streams of operations are created for each Observer

你会说,那我要给两个Observers发射相同的三个随机数,可以首先是在Observable.range()之后调用publish(),产生ConnectableObservable。然后,可以调用map(),然后是Observers调用connect()。但是,你看到了,不是你期望的结果。每个Observer得到了分开的随机数:

import io.reactivex.Observable;
import java.util.concurrent.ThreadLocalRandom;

public class Launcher {
    public static void main(String[] args) {
        ConnectableObservable<Integer> threeInts = Observable.range(1,3).publish();
        Observable<Integer> threeRandoms = threeInts.map(i -> randomInt());
        
        threeRandoms.subscribe(i -> System.out.println("Observer 1: " + i));
        threeRandoms.subscribe(i -> System.out.println("Observer 2: " + i));
        threeInts.connect();      
    }
    
    public static int randomInt() {
        return ThreadLocalRandom.current().nextInt(100000);
    }        
}

输出是

Observer 1: 94084
Observer 2: 94961
Observer 1: 2308
Observer 2: 84564
Observer 1: 9046
Observer 2: 78881

为什么呢?Observable.range()之后multicast,但是,multicasting发生在map()之前。map()之后,每个Observer仍然得到一个分开的流。publish()之前的任何事被组合成一个流(或者更技术一点,一个代理Observer)。
但是publish()之后,会fork成分开的流,如下图:
fork

如果我们想防止map()产生两个分开的流,我们需要在map()之后调用publish():

import io.reactivex.Observable;
import java.util.concurrent.ThreadLocalRandom;

public class Launcher {
    public static void main(String[] args) {
        ConnectableObservable<Integer> threeRandoms = Observable.range(1,3)
                .map(i -> randomInt()).publish();
        
        threeRandoms.subscribe(i -> System.out.println("Observer 1: " + i));
        threeRandoms.subscribe(i -> System.out.println("Observer 2: " + i));
        threeRandoms.connect();
    }
    
    public static int randomInt() {
        return ThreadLocalRandom.current().nextInt(100000);
    }        
}

输出是

Observer 1: 33845
Observer 2: 33845
Observer 1: 50389
Observer 2: 50389
Observer 1: 71504
Observer 2: 71504

终于对了!每个Observer得到了相同的三个随机数。现在,一个流实例通过了整个链,因为map()在publish()的前面,而不是后面:
behind

When to multicast

多个Observers的时候,Multicasting有利于防止冗余工作。也可以提高性能,减少内存和CPU的使用,或者能简化业务逻辑(当所有的Observers需要相同的emissions的时候)。
数据驱动的cold Observables应该只有在你想提高性能或者多个Observers接收相同数据的时候使用multicast。记住,multicasting增加hot ConnectableObservables,你不得不小心并且及时调用onnect(),这样Observers才不会错过数据。
甚至如果你的源Observable是hot(比如JavaFX或者Android的UI事件),putting operators也可能导致冗余工作和监听。当只有一个Observer的时候,用不着multicast,multicasting能导致不必要的负担。但是,如果有多个Observers,你需要找到可以multicast和合并上游操作的代理点。这个点是典型的边界,在这里,Observers有相同的上游操作,和不同的下游操作。
比如,你有一个Observer,用来打印随机数,但是另一个Observer使用reduce()求和。在这个点,一个流应该fork成两个分开的流,因为他们不再冗余,做的是不同的工作:

import io.reactivex.Observable;
import java.util.concurrent.ThreadLocalRandom;

public class Launcher {
    public static void main(String[] args) {
        ConnectableObservable<Integer> threeRandoms = Observable.range(1, 3)
                .map(i -> randomInt()).publish();
        //Observer 1 - print each random integer
        threeRandoms.subscribe(i -> System.out.println("Observer 1: " + i));
        //Observer 2 - sum the random integers, then print
        threeRandoms.reduce(0, (total, next) -> total + next)
                .subscribe(i -> System.out.println("Observer 2: " + i));
        threeRandoms.connect();
    }
    
    public static int randomInt() {
        return ThreadLocalRandom.current().nextInt(100000);
    }        
}

输出是

Observer 1: 96689
Observer 1: 9730
Observer 1: 86978
Observer 2: 193397

Automatic connection

有时候,你想手动调用ConnectableObservable的connect(),控制emissions开始发射的时间。允许Observable动态connect要小心点,这时候,Observers容易错过emissions。

autoConnect()

ConnectableObservable可以很方便地使用autoConnect()。对于一个给定的ConnectableObservable,调用autoConnect()会返回一个Observable,在一定数量的Observers订阅以后,它会自动调用connect()。前面的例子有两个Observers,可以在publish()之后,立刻调用autoConnect(2):

import io.reactivex.Observable;
import java.util.concurrent.ThreadLocalRandom;

public class Launcher {
    public static void main(String[] args) {
        Observable<Integer> threeRandoms = Observable.range(1, 3)
                .map(i -> randomInt())
                .publish()
                .autoConnect(2);
        //Observer 1 - print each random integer
        threeRandoms.subscribe(i -> System.out.println("Observer 1: " + i));
        //Observer 2 - sum the random integers, then print
        threeRandoms.reduce(0, (total, next) -> total + next)
                .subscribe(i -> System.out.println("Observer 2: " + i));
    }
    
    public static int randomInt() {
        return ThreadLocalRandom.current().nextInt(100000);
    }        
}

这样做,省掉了ConnectableObservable和后面的connect()调用。它会在得到两个订阅以后开始发射。
甚至当所有的下游Observers完成了或者dispose,autoConnect()会为源保存它的订阅。如果源finite并且disposes,如果新的Observer想订阅,它不会再次订阅源。
如果我们在上面的例子上增加第三个Observer,autoConnect()的参数还是2,第三个Observer会错过这些emissions:

import io.reactivex.Observable;
import java.util.concurrent.ThreadLocalRandom;

public class Launcher {
    public static void main(String[] args) {
        Observable<Integer> threeRandoms = Observable.range(1, 3)
                .map(i -> randomInt()).publish().autoConnect(2);
        
        //Observer 1 - print each random integer
        threeRandoms.subscribe(i -> System.out.println("Observer 1: " + i));
        //Observer 2 - sum the random integers, then print
        threeRandoms.reduce(0, (total, next) -> total + next).subscribe(i -> System.out.println("Observer 2: " + i));
        //Observer 3 - receives nothing
        threeRandoms.subscribe(i -> System.out.println("Observer 3: " + i));
    }
    
    public static int randomInt() {
        return ThreadLocalRandom.current().nextInt(100000);
    }        
}

如果你没传numberOfSubscribers参数,默认值是1。下面的例子,我们publish和autoConnect Observable.interval(),第一个Observer开始发射emissions,3秒以后,另一个Observer来了,但是会错过前面几个emissions,后面的都接收到了:

import io.reactivex.Observable;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable<Long> seconds = Observable.interval(1, TimeUnit.SECONDS)
                .publish()
                .autoConnect();
        //Observer 1
        seconds.subscribe(i -> System.out.println("Observer 1: " + i));
        sleep(3000);
        //Observer 2
        seconds.subscribe(i -> System.out.println("Observer 2: " + i));
        sleep(3000);
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

如果numberOfSubscribers参数是0,它会立刻开始发射,不等任何Observers。

refCount() and share()

ConnectableObservable的refCount()类似autoConnect(1),在得到一个订阅以后发射。但是,有一个重要的不同:当它不再有Observers的时候,会处置它自己,当有了新的订阅,就重新开始。当它不再有Observers的时候,它不保存对源的订阅,当来了另一个Observer,就重新开始。
让我们看个例子:有一个每秒发射的Observable.interval(),使用refCount()实现multicast。Observer 1接受了五个emissions,Observer 2接受了两个。我们错开这两个订阅,中间隔三秒钟。因为这两个订阅是有限的(由于take()),在Observer 3来的时候,他们应该已经终止了,此时,不应该再有之前的Observers。注意,Observer 3开始于0:

import io.reactivex.Observable;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable<Long> seconds =
                Observable.interval(1, TimeUnit.SECONDS)
                        .publish()
                        .refCount();
        //Observer 1
        seconds.take(5).subscribe(l -> System.out.println("Observer 1: " + l));
        sleep(3000);
        //Observer 2
        seconds.take(2).subscribe(l -> System.out.println("Observer 2: " + l));
        sleep(3000);
        //there should be no more Observers at this point
        //Observer 3
        seconds.subscribe(l -> System.out.println("Observer 3: " + l));
        sleep(3000);
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出是

Observer 1: 0
Observer 1: 1
Observer 1: 2
Observer 1: 3
Observer 2: 3
Observer 1: 4
Observer 2: 4
Observer 3: 0
Observer 3: 1
Observer 3: 2

也可以使用publish().refCount()的别名share():

Observable<Long> seconds =
        Observable.interval(1, TimeUnit.SECONDS).share();

Replaying and caching

Multicasting也允许缓存多个Observers之间共享的值。重播和缓存数据是一个multicasting活动,我们将会看到怎么做才是安全和有效率的。

Replaying

replay()是保存一定范围内的从前的emissions的一个强有力的办法,当来了新的Observer就重新发射他们。它会返回一个ConnectableObservable,既能multicast emissions,也能发射一定范围内的从前的emissions。它缓存的从前的emissions会在来了新的Observer的时候立刻发射,然后它会发射之后的emissions。
让我们从不带参数的replay()开始。它会为迟来的Observers重播所有的从前的emissions,然后发射新产生的。如果我们使用每秒发射的Observable.interval(),
调用replay()实现multicast,重播从前的整数emissions。因为replay()返回ConnectableObservable,我们使用autoConnect(),这样会在第一个订阅之后发射。
三秒以后,来了第二个Observer,看看发生了什么:

import io.reactivex.Observable;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable<Long> seconds =
                Observable.interval(1, TimeUnit.SECONDS)
                        .replay()
                        .autoConnect();
        //Observer 1
        seconds.subscribe(l -> System.out.println("Observer 1: " + l));
        sleep(3000);
        //Observer 2
        seconds.subscribe(l -> System.out.println("Observer 2: " + l));
        sleep(3000);
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出是

Observer 1: 0
Observer 1: 1
Observer 1: 2
Observer 2: 0
Observer 2: 1
Observer 2: 2
Observer 1: 3
Observer 2: 3
Observer 1: 4
Observer 2: 4
Observer 1: 5
Observer 2: 5

看到了吧?三秒以后,Observer 2来了,立刻收到错过的前面三个emissions:0、1和2.然后,它收到的emissions和Observer 1的一样。注意,这样做消耗大量的内存,replay()缓存了它接收到的全部emissions。如果源是无限的,或者只关心最后的一些emissions,可以指定bufferSize参数,限制重播的数量。如果我们调用replay(2),第二个Observer不会收到0,但是会收到1和2。
如果你甚至在没有订阅的时候,也想使用replay()保存缓存的值,就和autoConnect()结合,而不是refCount()。比如下面的例子,第二个Observer只能接收到最后的值:

import io.reactivex.Observable;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
                        .replay(1)
                        .autoConnect();
        //Observer 1
        source.subscribe(l -> System.out.println("Observer 1: " + l));
        //Observer 2
        source.subscribe(l -> System.out.println("Observer 2: " + l));        
    }
}

输出是

Observer 1: Alpha
Observer 1: Beta
Observer 1: Gamma
Observer 1: Delta
Observer 1: Epsilon
Observer 2: Epsilon

如果使用refCount(),而不是autoConnect(),看看会发生什么:

        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
                        .replay(1)
                        .refCount();

输出是

Observer 1: Alpha
Observer 1: Beta
Observer 1: Gamma
Observer 1: Delta
Observer 1: Epsilon
Observer 2: Alpha
Observer 2: Beta
Observer 2: Gamma
Observer 2: Delta
Observer 2: Epsilon

当Observer 1完成的时候,refCount()导致缓存(和整个链)被处置和复位,因为不再有Observers。当Observer 2来了,就像第一个一样,它重新开始发射一切,并且又建了另一个缓存。
replay()有其他重载,可以指定一个基于时间的window。下面,我们构造一个每300毫秒发射的Observable.interval(),订阅它。
我们还把每个发射的连续整数映射成一个过去的毫秒。我们为每个新的Observer重播最后一秒的emissions,新的Observer在两秒以后到来:

import io.reactivex.Observable;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable<Long> seconds =
                Observable.interval(300, TimeUnit.MILLISECONDS)
                        .map(l -> (l + 1) * 300) // map to elapsed milliseconds
                        .replay(1, TimeUnit.SECONDS)
                        .autoConnect();
        //Observer 1
        seconds.subscribe(l -> System.out.println("Observer 1: " + l));
        sleep(2000);
        //Observer 2
        seconds.subscribe(l -> System.out.println("Observer 2: " + l));
        sleep(1000);
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出是

Observer 1: 300
Observer 1: 600
Observer 1: 900
Observer 1: 1200
Observer 1: 1500
Observer 1: 1800
Observer 2: 1500
Observer 2: 1800
Observer 1: 2100
Observer 2: 2100
Observer 1: 2400
Observer 2: 2400
Observer 1: 2700
Observer 2: 2700
Observer 1: 3000
Observer 2: 3000

你也可以在时间间隔之上指定bufferSize参数,这样只有在时间周期内的一定数量的最后的emissions可以被缓存。如果我们修改
程序,只重播最后一秒内的一个emission,只能给Observer 2重播1800:

Observable<Long> seconds =
    Observable.interval(300, TimeUnit.MILLISECONDS)
            .map(l -> (l + 1) * 300) // map to elapsed milliseconds
            .replay(1, 1, TimeUnit.SECONDS)
            .autoConnect();

Caching

当你想使用ConnectableObservable缓存所有的无限的emissions,不需要控制订阅行为,可以使用cache()。它会在第一个下游Observer订阅源,该Observer订阅并且保存所有的值:

import io.reactivex.Observable;

public class Launcher {
    public static void main(String[] args) {
        Observable<Integer> cachedRollingTotals =
                Observable.just(6, 2, 5, 7, 1, 4, 9, 8, 3)
                        .scan(0, (total, next) -> total + next)
                        .cache();
        cachedRollingTotals.subscribe(System.out::println);    
    }
}

也可以调用cacheWithInitialCapacity(),指定希望缓存的元素数量:

Observable<Integer> cachedRollingTotals =
    Observable.just(6, 2, 5, 7, 1, 4, 9, 8, 3)
            .scan(0, (total,next) -> total + next)
            .cacheWithInitialCapacity(9);

Subjects

应该特别注意Subjects,初学者经常使用了错误的一个,导致复杂的情况。Subjects都是有一个Observer和一个Observable,作为一个代理mulitcasting设备工作(像事件总线)。他们在响应式编程中有他们的地位,但是,你应该在使用他们之前尽量排除其他可选项。Erik Meijer,ReactiveX的开发者,这样描述他们:响应式编程的可变变量。Subjects有时候是调和命令式范式和响应式范式的必要的工具。
在讨论什么时候用他们,什么时候不用他们之前,先看看他们做了什么。

PublishSubject

有一个Subject的实现,它是一个实现了Observable和Observer的抽象类型。这意味着你可以手动调用onNext()、onComplete()和onError(),它会把这些事件传给下游的Observers。
这个最简单的Subject类型是PublishSubject,像所有的Subjects,是hot,向它的下游Observers广播。其他Subject类型增加了更多行为,但是PublishSubject是一个“vanilla”类型。
我们可以声明一个Subject,增加一个Observer,映射它的长度,然后订阅它,然后调用onNext()传递这些字符串。我们也调用onComplete(),通知不再有事件:

import io.reactivex.subjects.PublishSubject;
import io.reactivex.subjects.Subject;

public class Launcher {
    public static void main(String[] args) {
        Subject<String> subject = PublishSubject.create();
        subject.map(String::length)
                .subscribe(System.out::println);

        subject.onNext("Alpha");
        subject.onNext("Beta");
        subject.onNext("Gamma");
        subject.onComplete();        
    }
}

上面的代码显示了Subjects像一台神奇的设备,能桥接命令式编程和响应式编程。下来,我们看什么时候应该使用Subjects,什么时候不应该使用。

When to use Subjects

可以使用Subjects,眼巴巴地订阅一个不知道数量的多个源Observables,合并成一个Observable。由于Subjects是一个Observer,你能早一点把他们传给subscribe()方法。它在模块化的代码中很有用,其中难以解耦Observables和Observers,也不容易执行Observable.merge()。
下面的例子,使用Subject来merge和multicast两个Observable.interval源:

import io.reactivex.Observable;
import io.reactivex.subjects.PublishSubject;
import io.reactivex.subjects.Subject;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source1 =
                Observable.interval(1, TimeUnit.SECONDS)
                        .map(l -> (l + 1) + " seconds");
        Observable<String> source2 =
                Observable.interval(300, TimeUnit.MILLISECONDS)
                        .map(l -> ((l + 1) * 300) + " milliseconds");
        Subject<String> subject = PublishSubject.create();
        subject.subscribe(System.out::println);
        source1.subscribe(subject);
        source2.subscribe(subject);
        sleep(3000);
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出是

300 milliseconds
600 milliseconds
900 milliseconds
1 seconds
1200 milliseconds
1500 milliseconds
1800 milliseconds
2 seconds
2100 milliseconds
2400 milliseconds
2700 milliseconds
3 seconds
3000 milliseconds

当然,使用Observable.merge()也能完成它。但是,当你使用依赖注入或者其他解耦机制管理模块化的代码的时候,你可能没有准备好的Observable源放进Observable.merge()。比如,有一个JavaFX程序,来了一个来自菜单条、按钮或者按键组合的事件。我能声明这些事件源当作Observables,然后订阅到Subject,这样,不用硬耦合就可以组合这些事件流。
另一个要注意的是,Subject上的第一个Observable调用onComplete()会导致其他Observables的发射,并且忽略下游的cancellation请求。这意味着Subjects最好用于无限的、事件驱动的Observables。

When Subjects go wrong

你可能觉得前面发射Alpha、Beta和Gamma的Subject例子,是反直觉的。直到所有的Observers都设置好,我们才定义源,处理不再是从左到右、从上到下读。
由于Subjects是hot,在Observers被设置前执行onNext()调用会导致错过这些emissions。如果你这样移动onNext(),不会有任何输出:

import io.reactivex.subjects.PublishSubject;
import io.reactivex.subjects.Subject;

public class Launcher {
    public static void main(String[] args) {
        Subject<String> subject = PublishSubject.create();
        subject.onNext("Alpha");
        subject.onNext("Beta");
        subject.onNext("Gamma");
        subject.onComplete();
        subject.map(String::length)
                .subscribe(System.out::println);     
    }
}

所以,Subjects使用不当是危险的,特别是如果你把他们暴露给你的整个代码库,任何外部代码都可以调用onNext()传递emissions的时候。
例如,Subject被暴露给一个外部API,以至于其他代码能在Alpha、Beta和Gamma之前传递Puppy。如果我们希望源只发射这些希腊字母,显然会收到不想要的emissions。响应式编程只维护良好定义的、可预测的源的发射数据的完整性。Subjects不是一次性的,没有dispose()方法,下游调用dispose()的时候,也不会释放源。

Serializing Subjects

使用Subjects,要注意的一个关键问题是:onSubscribe()、onNext()、 onError()和onComplete()调用不是线程安全的。如果你有多个线程调用这四个方法,emissions可能被覆盖,破坏Observable契约(应该顺序发射emissions)。这种情况下,可以使用toSerialized()产生一个安全的序列化的Subject实现(由私有的SerializedSubject支持)。它会安全地序列化并发的事件调用:

Subject<String> subject =
        PublishSubject.<String>create().toSerialized();

BehaviorSubject

BehaviorSubject的行为和PublishSubject很像,但是向每个新来的下游Observer重播最后发射的item。有些像PublishSubject后面跟了一个
replay(1).autoConnect(),但是,它把上面的这些操作合并进一个经过优化的Subject实现:

import io.reactivex.subjects.BehaviorSubject;
import io.reactivex.subjects.Subject;

public class Launcher {
    public static void main(String[] args) {
        Subject<String> subject =
                BehaviorSubject.create();
        subject.subscribe(s -> System.out.println("Observer 1: " + s));
        subject.onNext("Alpha");
        subject.onNext("Beta");
        subject.onNext("Gamma");
        subject.subscribe(s -> System.out.println("Observer 2: " + s));        
    }
}

输出是

Observer 1: Alpha
Observer 1: Beta
Observer 1: Gamma
Observer 2: Gamma

你看到了,Observer 2虽然错过了Observer 1收到的三个emissions,但还是收到最后一个emission。如果你发现你需要一个Subject,
并且想为新来的Observers缓存最后一个emission,就可以使用BehaviorSubject。

ReplaySubject

ReplaySubject类似后面紧跟着cache()的PublishSubject。它不管下游是否有Observers,立刻捕获emissions,优化Subject内部的缓存:

import io.reactivex.subjects.ReplaySubject;
import io.reactivex.subjects.Subject;

public class Launcher {
    public static void main(String[] args) {
        Subject<String> subject =
                ReplaySubject.create();
        subject.subscribe(s -> System.out.println("Observer 1: " + s));
        subject.onNext("Alpha");
        subject.onNext("Beta");
        subject.onNext("Gamma");
        subject.onComplete();
        subject.subscribe(s -> System.out.println("Observer 2: " + s));        
    }
}

输出是

Observer 1: Alpha
Observer 1: Beta
Observer 1: Gamma
Observer 2: Alpha
Observer 2: Beta
Observer 2: Gamma

AsyncSubject

AsyncSubject有一个量身定制的,特定于有限(finite-specific)的行为:它只发射接收到的最后的值,然后是onComplete()事件:

import io.reactivex.subjects.AsyncSubject;
import io.reactivex.subjects.Subject;

public class Launcher {
    public static void main(String[] args) {
        Subject<String> subject =
                AsyncSubject.create();
        subject.subscribe(s -> System.out.println("Observer 1: " + s),
                Throwable::printStackTrace,
                () -> System.out.println("Observer 1 done!")
        );
        subject.onNext("Alpha");
        subject.onNext("Beta");
        subject.onNext("Gamma");
        subject.onComplete();
        subject.subscribe(s -> System.out.println("Observer 2: " + s),
                Throwable::printStackTrace,
                () -> System.out.println("Observer 2 done!")
        );        
    }
}

UnicastSubject

UnicastSubject,就像所有的Subjects,用来观察和订阅源。但是,它会buffer所有的emissions等Observer订阅它,然后会把这些emissions发射给该Observer,清自己的缓存:

import io.reactivex.Observable;
import io.reactivex.subjects.ReplaySubject;
import io.reactivex.subjects.Subject;
import io.reactivex.subjects.UnicastSubject;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Subject<String> subject =
                UnicastSubject.create();
        Observable.interval(300, TimeUnit.MILLISECONDS)
                .map(l -> ((l + 1) * 300) + " milliseconds")
                .subscribe(subject);
        sleep(2000);
        subject.subscribe(s -> System.out.println("Observer 1: " + s));
        sleep(2000);        
    }


    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

当你运行上面代码的时候,你会看到两秒以后,前六个emissions发射给放订阅的Observer。然后,他会收到新的emissions。
但是,UnicastSubject有一个重要属性:它只为一个Observer服务,如果有后来者,就抛错误。这是因为它的内部队列一旦有了一个Observer,缓存就被释放了。如果你想要第二个Observer接收错过的emissions,可以这样欺骗他它:通过调用publish()来增加一个Observer代理,multicasts给更多的Observer:

import io.reactivex.Observable;
import io.reactivex.subjects.ReplaySubject;
import io.reactivex.subjects.Subject;
import io.reactivex.subjects.UnicastSubject;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Subject<String> subject = UnicastSubject.create();
        Observable.interval(300, TimeUnit.MILLISECONDS)
                .map(l -> ((l + 1) * 300) + " milliseconds")
                .subscribe(subject);
        sleep(2000);
        //multicast to support multiple Observers
        Observable<String> multicast = subject.publish().autoConnect();
        //bring in first Observer
        multicast.subscribe(s -> System.out.println("Observer 1: " + s));
        sleep(2000);
        //bring in second Observer
        multicast.subscribe(s -> System.out.println("Observer 2: " + s));
        sleep(1000);     
    }


    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_43364172/article/details/84246785
今日推荐