我正在玩rxJava / rxAndroid,并且有一些非常基本的东西不像我期望的那样。 我有一个可观察的和两个订阅者:
Observable<Integer> dataStream = Observable.just(1, 2, 3).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
Log.d(TAG, "subscribing sub1...");
dataStream.subscribe(v -> System.out.println("Subscriber #1: "+ integer));
Log.d(TAG, "subscribing sub2...");
dataStream.subscribe(v -> System.out.println("Subscriber #2: "+ integer));
这是输出:
D/RxJava: subscribing sub1...
D/RxJava: subscribing sub2...
D/RxJava: Subscriber #1: 1
D/RxJava: Subscriber #1: 2
D/RxJava: Subscriber #1: 3
D/RxJava: Subscriber #2: 1
D/RxJava: Subscriber #2: 2
D/RxJava: Subscriber #2: 3
现在,我知道我可以使用publish().autoConnect()
来避免重复计数,但我首先想要了解这种默认行为。
每当有人订阅observable时,它就会开始发出数字序列。我明白了。因此,当Subscriber 1
连接时,它开始发出项目。 Subscriber 2
立即连接,为什么不能获得这些值呢?
这是我理解它的方式,从可观察的角度来看:
有人订阅了我,我应该开始发物品了 [订阅者:1] [发明项目:1,2,3]
向订阅者发送项目'1' [订阅者:1] [发明项目:2,3]
其他人订阅了我,当我完成后,我会再次发出1,2,3 [订阅者:1&amp; 2] [项目到EMIT:2,3,1,2,3]
向订阅者发送项目'2' [订阅者:1&amp; 2] [项目到EMIT:3,1,2,3]
向订阅者发送项目'3' [订阅者:1&amp; 2] [项目到EMIT:1,2,3]
向订阅者发送项目'1' [订阅者:1&amp; 2] [项目到EMIT:2,3]
...
但这不是它的工作原理。 就像它们是两个独立的可观察者一样。这让我很困惑,他们为什么不把这些物品交给所有订户?
加成:
publish().autoConnect()
如何解决问题?
让我们分解吧。 publish()
给了我一个可连接的可观察对象。可连接的observable就像一个常规的observable,但你可以告诉它何时连接。然后我继续通过拨打autoConnect()
通过这样做......难道我没有得到与我开始时相同的东西吗?一个普通的常规观察。 运营商似乎互相取消。
我可以闭嘴并使用publish().autoconnect()
。但我想更多地了解可观察物是如何工作的。
谢谢!
答案 0 :(得分:15)
这是因为实际上这是两个独立的可观察者。他们是'#34;产生&#34;当你调用subscribe()
时。因此,您提供的步骤在第3步和第3步的意义上是不正确的。 4只是1&amp; 2但是在不同的观察上。
但是你看到它们是1 1 1 2 2 2因为记录发生的线程。如果您要移除observeOn()
部分,那么您将以交织的方式看到排放。要查看下面的运行代码:
@Test
public void test() throws InterruptedException {
final Scheduler single = Schedulers.single();
final long l = System.nanoTime();
Observable<Long> dataStream =
Observable.just(1, 2, 3)
.map(i -> System.nanoTime())
.subscribeOn(Schedulers.computation());
//.observeOn(single);
dataStream.subscribe(i -> System.out.println("1 " + Thread.currentThread().getName() + " " + (i - l)));
dataStream.subscribe(i -> System.out.println("2 " + Thread.currentThread().getName() + " " + (i - l)));
Thread.sleep(1000);
}
输出,至少在我的运行中是(注意线程名称):
1 RxComputationThreadPool-1 135376988
2 RxComputationThreadPool-2 135376988
1 RxComputationThreadPool-1 135486815
2 RxComputationThreadPool-2 135537383
1 RxComputationThreadPool-1 135560691
2 RxComputationThreadPool-2 135617580
如果您应用observeOn()
,则会变为:
1 RxSingleScheduler-1 186656395
1 RxSingleScheduler-1 187919407
1 RxSingleScheduler-1 187923753
2 RxSingleScheduler-1 186656790
2 RxSingleScheduler-1 187860148
2 RxSingleScheduler-1 187864889
正如您已经正确指出的那样,为了获得您想要的内容,您需要publish().refcount()
或简单地share()
(它是别名)运算符。
这是因为publish()
创建了ConnectableObservable
,在通过connect()
方法告知之前,它不会开始发出项目。在这种情况下,如果你这样做:
@Test
public void test() throws InterruptedException {
final Scheduler single = Schedulers.single();
final long l = System.nanoTime();
ConnectableObservable<Long> dataStream =
Observable.just(1, 2, 3)
.map(i -> System.nanoTime())
.subscribeOn(Schedulers.computation())
.observeOn(single)
.publish();
dataStream.subscribe(i -> System.out.println("1 " + (i - l)));
dataStream.subscribe(i -> System.out.println("2 " + (i - l)));
Thread.sleep(1000);
dataStream.connect();
Thread.sleep(1000);
}
你会注意到,第一秒(第一次Thread.sleep()
调用)没有任何反应,就在调用dataStream.connect()
之后,排放就会发生。
refCount()
接收一个ConnectableObservable,并通过计算当前订阅的订阅者数来向订阅者隐藏调用connect()
的需要。它的作用是在第一次订阅时调用connect()
并且在最后取消订阅之后取消订阅原始可观察对象。
关于publish().autoConnect()
的相互取消,之后你会得到一个可观察的但是它有一个特殊的属性,比如原来的observable通过互联网进行API调用(持续10秒),当你使用时如果没有share()
,那么您将最终获得与服务器一样多的并行查询,因为这些订阅超过了10秒。另一方面,share()
只有一个电话。
如果共享的可观察对象以非常快的速度完成其工作(例如just(1,2,3)
),您将看不到任何好处。
autoConnect()
/ refCount()
为您提供了一个您订阅的中间可观察对象,而不是原始的可观察对象。
如果您有兴趣深入了解本书:Reactive Programming with RxJava
答案 1 :(得分:9)
Observable
的核心是subscribe
功能。每次新观察者订阅时,它都会作为参数传递给此函数。该功能的作用是将数据提供给单个观察者。它通过调用observer.onNext
方法完成此操作。它可以立即执行此操作(如just
),或通过某些调度程序(例如interval
),或从后台线程或回调(例如,通过启动某些异步任务)。
我在上面突出显示单词 single ,因为这是该函数调用时唯一的观察者。如果您多次订阅此类可观察对象,则会为每个订阅者调用其subscribe
函数。
这样的数据源称为冷可观察。
应用subscribeOn
运算符会在您的subscribe
调用和原始可观察的subscribe
函数之间添加中间步骤。您不再直接呼叫它,而是通过指定的调度程序安排呼叫。
observeOn
为您的观察者的所有onNext
次调用添加了类似的中间步骤。
在您的示例中,subscribe
函数被调用两次,即数据系列生成两次。调用是通过多线程io
调度程序调度的,因此这些调用不是在主线程上发生,而是在其他两个线程上发生,几乎同时发生。两个线程都开始调用两个订户的onNext
方法。请记住,每个线程只知道自己的订户。 onNext
调度由mainThread
调度程序调度,这是单线程的,即它们不能同时发生但需要以某种方式排队。 严格来说,无法保证这些电话的订购。它取决于各种因素,具体是针对具体实施的。尝试将just
替换为interval
(这会在消息之间引入延迟),并且您会看到消息将以不同的顺序到达。
publish
运算符使您的observable hot ,又名可连接。它为subscribe
函数添加了中间步骤 - 仅调用一次,并向onNext
方法调用 - 这些将传播到所有已订阅的可观察对象。换句话说,它允许多个订阅者共享单个订阅。
准确地说,当您调用subscribe
方法时,会调用connect
函数。有两个运算符会自动为您调用connect
:
autoConnect
在第一个订阅者进入时调用connect
方法。但它永远不会断开连接。refCount
在第一个订阅者进入时调用connect
,并在最后一个订阅者取消订阅时自动断开连接。当新订阅者进来时,它将重新连接(再次调用subscribe
功能)。 publish().refCount()
是受欢迎的组合,因此它有快捷方式:share()
。
对于您的教育,请尝试使用以下代码:share
:
Observable<Long> dataStream = Observable.interval(100, TimeUnit.MILLISECONDS)
.take(3)
.share();
System.out.println("subscribing A");
dataStream.subscribe(v -> System.out.println("A got " + v));
TimeUnit.MILLISECONDS.sleep(150);
System.out.println("subscribing B");
dataStream.subscribe(v -> System.out.println("B got " + v));
TimeUnit.SECONDS.sleep(1);
1)冷观察总是处理单个用户。所以你的时间图应该是这样的:
subscribed first subscriber
[SUBSCRIBER: 1][ITEMS TO EMIT: 1,2,3]
subscribed second subscriber
[SUBSCRIBER: 1][ITEMS TO EMIT: 1,2,3]
[SUBSCRIBER: 2][ITEMS TO EMIT: 1,2,3]
emit "1" to subscriber 1
[SUBSCRIBER: 1][ITEMS TO EMIT: 2,3]
[SUBSCRIBER: 2][ITEMS TO EMIT: 1,2,3]
emit "1" to subscriber 2
[SUBSCRIBER: 1][ITEMS TO EMIT: 2,3]
[SUBSCRIBER: 2][ITEMS TO EMIT: 2,3]
...
虽然由于多线程比赛而无法保证订单。
2)publish
和autoConnect
互不取消。他们只是添加。
dataSource = ...;
dataSourceShared = dataSource.publish().autoConnect();
现在,当您为dataSourceShared
订阅多个订阅者时,这只会导致原始dataSource
的一个订阅。即您不必为每个新订阅者发出新的消息系列。