web-dev-qa-db-ja.com

複数のサブスクライバーで単一のオブザーバブル

Retrofit Serviceから作成されたObservable<<List<Foo>> getFoo()があり、.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()のこの種の動作は、Retrofit 1.9まで正常に機能していました。 Retrofit 2-ベータ版の動作を停止しました。数時間前にリリースされたRetrofit 2リリースバージョンではまだテストしていません。

編集:2017年1月2日

将来の読者のために、私は記事を書きました here 事件についてもっと説明します!

26
Pavlos

.share()によって返されたConnectedObservableを通常の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()を呼び出す必要はありません。オブザーバブルを起動するためにのみ必要です。 replay()を使用して、後続のすべてのサブスクライバーがすべてのアイテムを生成することを確認できると思います

_ConnectedObservable<List<Contributor>> testObservable = retrofit
        .create(GitHub.class)
        .contributors("square", "retrofit")
        .share()
        .replay()
_
26
JohnWowUs

RxJava開発者DávidKarnokに戻って確認した後、ここで何が起こっているかについて完全な説明を提案したいと思います。

share()publish().refCount()として定義されます。 e。ソースObservableは最初にpublish()によってConnectableObservableに変換されますが、connect()を「手動で」呼び出す代わりに、その部分はrefCount()。特に、refCountは、最初のサブスクリプションを受け取ると、ConnectableObservableconnect()を呼び出します。その後、少なくとも1人のサブスクライバーがいる限り、サブスクライブされたままになります。そして、最後に、サブスクライバーの数が0に下がると、サブスクライバーは上方にサブスクリプションを解除します。 coldObservablesを使用すると、Retrofitによって返されるものと同様に、実行中の計算が停止します。

これらのサイクルのいずれか後に別のサブスクライバーが登場すると、refCountが再びconnectを呼び出し、ソースObservableへの新しいサブスクリプションをトリガーします。この場合、別のネットワーク要求がトリガーされます。

現在、これは通常、Retrofit 1(および実際には this commit )より前のバージョンでは明らかになりませんでした。これらの古いバージョンのRetrofitはデフォルトですべてのネットワーク要求を別のスレッドに移動したためです。これは通常、最初のリクエスト/ Observableがまだ実行中にすべてのsubscribe()呼び出しが発生するため、新しいSubscribersが単にrefCountしたがって、追加のリクエストをトリガーしません/ Observables

ただし、Retrofitの新しいバージョンでは、デフォルトで作業を別のスレッドに移動しません。たとえば、subscribeOn(Schedulers.io())を呼び出して明示的に移動する必要があります。そうしないと、すべてが現在のスレッドに留まります。つまり、2番目のsubscribe()は、最初のObservableonCompletedを呼び出した後にのみ発生するため、 Subscribersはサブスクライブを解除し、すべてがシャットダウンされます。さて、最初の段落で見たように、2番目のsubscribe()が呼び出されると、share()には選択肢がありませんが、ソースObservableに別のSubscriptionを引き起こし、別のネットワーク要求。

ですから、Retrofit 1から慣れた動作に戻るには、subscribeOn(Schedulers.io())を追加するだけです。

これにより、ほとんどの場合、ネットワーク要求のみが実行されます。ただし、原則として、ネットワーク要求が非常に高速である場合、および/またはsubscribe()呼び出しがかなりの遅延で発生する場合にのみ、複数の要求を取得できます(Retrofit 1を使用すると常に取得できます)。再度、2番目のsubscribe()が発生すると、最初の要求は終了します。

したがって、Dávidはcache()(ただし、あなたが言及した欠点があります)またはreplay().autoConnect()を使用することをお勧めします。これらの リリースノート によると、autoConnectrefCountの前半だけのように機能します。より正確には、

refCount()と動作が似ていますが、サブスクライバが失われても切断されない点が異なります。

つまり、最初のsubscribe()が発生したときにのみリクエストがトリガーされますが、その後のSubscribersは、サブスクライバーが0であるかどうかに関係なく、発行されたすべてのアイテムを受け取ります。

39
david.mihola