我有一个Observable<<List<Foo>> getFoo()
,它是从Retrofit服务创建的,并且在调用之后
.getFoo()
方法,我需要与多个订阅者共享它。但是,调用.share()
方法会导致重新执行网络呼叫。重播运算符也不起作用。我知道潜在的解决方案可能是.cache()
,但我不知道为什么会导致这种行为。
// Create an instance of our GitHub API interface.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
// Create a call instance for looking up Retrofit contributors.
Observable<List<Contributor>> testObservable = retrofit
.create(GitHub.class)
.contributors("square", "retrofit")
.share();
Subscription subscription1 = testObservable
.subscribe(new Subscriber<List<Contributor>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onNext(List<Contributor> contributors) {
System.out.println(contributors);
}
});
Subscription subscription2 = testObservable
.subscribe(new Subscriber<List<Contributor>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onNext(List<Contributor> contributors) {
System.out.println(contributors + " -> 2");
}
});
subscription1.unsubscribe();
subscription2.unsubscribe();
上面的代码可以重现上述行为。您可以对其进行调试,并查看收到的列表属于不同的MemoryAddress。
我还将ConnectableObservables视为一种潜在的解决方案,但这需要我携带原始的observable,并在每次添加新订阅者时调用.connect()
。
.share()
的这种行为在改造1.9之前一直很好。它停止了Retrofit 2-beta的工作。我还没有使用几小时前发布的Retrofit 2 Release Version进行测试。
编辑:01/02/2017
对于未来的读者,我写了一篇文章here,详细解释了这个案例!
答案 0 :(得分:38)
在与RxJava开发人员DávidKarnok核实后,我想提出一个完整的解释,说明这里发生了什么。
share()
定义为publish().refCount()
,i。即源Observable
首先由ConnectableObservable
转换为publish()
,但不必手动调用connect()
,该部分由refCount()
处理。特别是,refCount
会在connect()
收到第一个订阅时调用ConnectableObservable
;那么,只要有至少一个用户,它就会保留订阅;最后,当订阅者数量下降到0时,它将取消订阅。使用 cold Observables
,就像Retrofit返回的那样,这将停止任何正在运行的计算。
如果在其中一个订阅者出现其中一个订阅者之后,refCount
将再次调用connect
,从而触发对源Observable的新订阅。在这种情况下,它将触发另一个网络请求。
现在,这通常不会在Retrofit 1(以及this commit之前的任何版本)中变得明显,因为默认情况下这些旧版本的Retrofit会将所有网络请求移动到另一个线程。这通常意味着当第一个请求/ subscribe()
仍在运行时,您的所有Observable
来电都会发生,因此新的Subscriber
只会被添加到refCount
,因此不会触发其他请求/ Observables
。
然而,较新版本的Retrofit不会默认将工作移动到另一个线程 - 您必须通过调用subscribeOn(Schedulers.io())
来明确地执行此操作。如果不这样做,一切都将保留在当前线程上,这意味着第二个subscribe()
只会在第一个Observable
调用onCompleted
之后发生,因此在Subscribers
之后取消订阅,一切都关闭了。现在,正如我们在第一段中看到的,当第二个subscribe()
被调用时,share()
别无选择,只能使另一个Subscription
到源Observable并触发另一个网络请求。
因此,要回到您在Retrofit 1中习惯的行为,只需添加subscribeOn(Schedulers.io())
。
这应该导致只执行网络请求 - 大部分时间。原则上,您仍然可以获得多个请求(并且您可以使用Retrofit 1),但只有当您的网络请求非常快且/或subscribe()
调用发生相当大的延迟时,才会再次当第二个subscribe()
发生时,第一个请求就完成了。
因此,Dávid建议使用cache()
(但它有你提到的缺点)或replay().autoConnect()
。根据这些release notes,autoConnect
仅与refCount
的前半部分相同,或者更准确地说,它是
与refCount()的行为类似,但它不会断开连接 订阅者丢失时。
这意味着只有在第一个subscribe()
发生时触发请求,但随后所有后续Subscriber
将接收所有发出的项目,无论是否有任何时间,0个订阅者
答案 1 :(得分:25)
您似乎(隐含地)将ConnectedObservable
返回的.share()
投回到正常的Observable
。您可能想要了解热和冷可观测量之间的区别。
尝试
ConnectedObservable<List<Contributor>> testObservable = retrofit
.create(GitHub.class)
.contributors("square", "retrofit")
.share();
Subscription subscription1 = testObservable
.subscribe(new Subscriber<List<Contributor>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onNext(List<Contributor> contributors) {
System.out.println(contributors);
}
});
Subscription subscription2 = testObservable
.subscribe(new Subscriber<List<Contributor>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onNext(List<Contributor> contributors) {
System.out.println(contributors + " -> 2");
}
});
testObservable.connect();
subscription1.unsubscribe();
subscription2.unsubscribe();
编辑:每次想要新订阅时都不需要调用connect()
,只需启动它就可以启动observable。我想您可以使用replay()
来确保所有后续订阅者都能获得所有项目
ConnectedObservable<List<Contributor>> testObservable = retrofit
.create(GitHub.class)
.contributors("square", "retrofit")
.share()
.replay()