具有多个订户的单个可观察者

时间:2016-03-11 23:50:55

标签: android retrofit rx-java

我有一个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,详细解释了这个案例!

2 个答案:

答案 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 notesautoConnect仅与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()