web-dev-qa-db-ja.com

要求のRxJava結合シーケンス

問題

Apisが2つあります。 Api 1は、アイテムのリストを提供し、Api 2は、Api 1から取得した各アイテムの詳細な情報を提供します。これまでに解決した方法では、パフォーマンスが低下します。

質問

RetrofitとRxJavaの助けを借りて、この問題を効率的かつ迅速に解決します。

私のアプローチ

瞬間に私のソリューションは次のようになります:

ステップ1:レトロフィットの実行Single<ArrayList<Information>> Api 1から。

ステップ2:このアイテムを繰り返し処理し、Api 2にそれぞれをリクエストします。

ステップ3:レトロフィット返品Single<ExtendedInformation>各アイテム

ステップ4:Api 2からのすべての呼び出しが完全に実行された後、情報と拡張情報を組み合わせたすべてのアイテムの新しいオブジェクトを作成します。

私のコード

 public void addExtendedInformations(final Information[] informations) {
        final ArrayList<InformationDetail> informationDetailArrayList = new ArrayList<>();
        final JSONRequestRatingHelper.RatingRequestListener ratingRequestListener = new JSONRequestRatingHelper.RatingRequestListener() {
            @Override
            public void onDownloadFinished(Information baseInformation, ExtendedInformation extendedInformation) {
                informationDetailArrayList.add(new InformationDetail(baseInformation, extendedInformation));
                if (informationDetailArrayList.size() >= informations.length){
                    listener.onAllExtendedInformationLoadedAndCombined(informationDetailArrayList);
                }
            }
        };

        for (Information information : informations) {
            getExtendedInformation(ratingRequestListener, information);
        }
    }

    public void getRatingsByTitle(final JSONRequestRatingHelper.RatingRequestListener ratingRequestListener, final Information information) {
        Single<ExtendedInformation> repos = service.findForTitle(information.title);
        disposable.add(repos.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribeWith(new DisposableSingleObserver<ExtendedInformation>() {
            @Override
            public void onSuccess(ExtendedInformation extendedInformation) {
                    ratingRequestListener.onDownloadFinished(information, extendedInformation);
            }

            @Override
            public void onError(Throwable e) {
                ExtendedInformation extendedInformation = new ExtendedInformation();
                ratingRequestListener.onDownloadFinished(extendedInformation, information);
            }
        }));
    }

    public interface RatingRequestListener {

        void onDownloadFinished(Information information, ExtendedInformation extendedInformation);

    }
21

tl; drconcatMapEagerまたはflatMapを使用して、サブコールを非同期またはスケジューラで実行します。


長い話

私はAndroid開発者ではないので、私の質問は純粋なRxJava(バージョン1およびバージョン2)に限定されます。

私が写真を正しく取得する場合、必要なフローは次のとおりです。

some query param 
  \--> Execute query on API_1 -> list of items
          |-> Execute query for item 1 on API_2 -> extended info of item1
          |-> Execute query for item 2 on API_2 -> extended info of item1
          |-> Execute query for item 3 on API_2 -> extended info of item1
          ...
          \-> Execute query for item n on API_2 -> extended info of item1
  \----------------------------------------------------------------------/
      |
      \--> stream (or list) of extended item info for the query param

Retrofitがクライアントを生成すると仮定して

interface Api1 {
    @GET("/api1") Observable<List<Item>> items(@Query("param") String param);
}

interface Api2 {
    @GET("/api2/{item_id}") Observable<ItemExtended> extendedInfo(@Path("item_id") String item_id);
}

アイテムの順序が重要でない場合、flatMapのみを使用できます。

api1.items(queryParam)
    .flatMap(itemList -> Observable.fromIterable(itemList)))
    .flatMap(item -> api2.extendedInfo(item.id()))
    .subscribe(...)

ただし、場合のみレトロフィットビルダーは

  • 非同期アダプターのいずれか(呼び出しはokhttp内部エグゼキューターのキューに入れられます)。あなたはこのエグゼキューターをコントロールできないので、個人的にはこれは良い考えではないと思います。

    .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()
    
  • または、スケジューラベースのアダプターを使用します(呼び出しはRxJavaスケジューラーでスケジュールされます)。使用するスケジューラを明示的に選択するため、これは私の優先オプションです。ほとんどの場合、IOスケジューラですが、別のスケジューラを自由に試すことができます。

    .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
    

理由は、flatMapapi2.extendedInfo(...)によって作成された各オブザーバブルをサブスクライブし、それらを結果のオブザーバブルにマージするからです。そのため、結果は受け取った順に表示されます。

Ifレトロフィットクライアントではない非同期に設定するか、スケジューラで実行するように設定すると、1つを設定することができます。

api1.items(queryParam)
    .flatMap(itemList -> Observable.fromIterable(itemList)))
    .flatMap(item -> api2.extendedInfo(item.id()).subscribeOn(Schedulers.io()))
    .subscribe(...)

この構造は、それが示すlocally各スケジューラがどのapi2.extendedInfoを実行することになっているかを示す以前のものとほぼ同じです。

maxConcurrencyflatMapパラメーターを調整して、同時に実行するリクエストの数を制御することができます。これには注意が必要ですが、すべてのクエリを同時に実行することは望ましくありません。通常、デフォルトのmaxConcurrencyで十分です(128)。

元のクエリの順序が重要な場合concatMapは通常flatMapと同じことを順番に行う演算子ですが、すべてのサブクエリが実行されるのをコードが待機する必要がある場合は遅くなります。ただし、解決策はconcatMapEagerを使用してさらに一歩進められ、これはobservableを順番にサブスクライブし、必要に応じて結果をバッファリングします。

レトロフィットクライアントが非同期であるか、特定のスケジューラで実行されていると仮定します。

api1.items(queryParam)
    .flatMap(itemList -> Observable.fromIterable(itemList)))
    .concatMapEager(item -> api2.extendedInfo(item.id()))
    .subscribe(...)

または、スケジューラをローカルに設定する必要がある場合:

api1.items(queryParam)
    .flatMap(itemList -> Observable.fromIterable(itemList)))
    .concatMapEager(item -> api2.extendedInfo(item.id()).subscribeOn(Schedulers.io()))
    .subscribe(...)

この演算子で並行性を調整することもできます。


さらに、ApiがFlowableを返している場合、RxJava 2.1.7では現時点でまだベータ版である.parallelを使用できます。しかし、結果は整然としておらず、後でソートすることなくそれらを順序付ける方法がわかりません(まだ?)。

api.items(queryParam) // Flowable<Item>
   .parallel(10)
   .runOn(Schedulers.io())
   .map(item -> api2.extendedInfo(item.id()))
   .sequential();     // Flowable<ItemExtended>
23
Brice

flatMap演算子は、これらのタイプのワークフローに対応するように設計されています。

簡単な5つのステップの例で広範なストロークの概要を説明します。コード内で同じ原則を簡単に再構築できることを願っています:

@Test fun flatMapExample() {
    // (1) constructing a fake stream that emits a list of values
    Observable.just(listOf(1, 2, 3, 4, 5))
            // (2) convert our List emission into a stream of its constituent values 
            .flatMap { numbers -> Observable.fromIterable(numbers) }
            // (3) subsequently convert each individual value emission into an Observable of some 
            //     newly calculated type
            .flatMap { number ->
                when(number) {
                       1 -> Observable.just("A1")
                       2 -> Observable.just("B2")
                       3 -> Observable.just("C3")
                       4 -> Observable.just("D4")
                       5 -> Observable.just("E5")
                    else -> throw RuntimeException("Unexpected value for number [$number]")
                }
            }
            // (4) collect all the final emissions into a list
            .toList()
            .subscribeBy(
                    onSuccess = {
                        // (5) handle all the combined results (in list form) here
                        println("## onNext($it)")
                    },
                    onError = { error ->
                        println("## onError(${error.message})")
                    }
            )
}

(偶然、放出の順序が重要な場合は、代わりにconcatMapを使用してください)。

それがお役に立てば幸いです。

3
homerman

以下を確認してください。

たとえば、Githubユーザー情報やGithubユーザーイベントを取得するために必要な複数のネットワークコールがあります。

そして、それぞれが戻るのを待ってからUIを更新します。ここでRxJavaが役立ちます。最初にRetrofitオブジェクトを定義してGithubのAPIにアクセスし、次に2つのネットワーク要求呼び出しに対して2つのオブザーバブルを設定しましょう。

Retrofit repo = new Retrofit.Builder()
        .baseUrl("https://api.github.com")
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        .build();

Observable<JsonObject> userObservable = repo
        .create(GitHubUser.class)
        .getUser(loginName)
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread());

Observable<JsonArray> eventsObservable = repo
        .create(GitHubEvents.class)
        .listEvents(loginName)
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread());

以下のようなインターフェースを使用しました:

public interface GitHubUser {
  @GET("users/{user}")
  Observable<JsonObject> getUser(@Path("user") String user);
}

public interface GitHubEvents {
  @GET("users/{user}/events")
  Observable<JsonArray> listEvents(@Path("user") String user);
}

RxJavaのZipメソッドを使用して2つのObservableを結合し、それらが完了するのを待ってから新しいObservableを作成した後。

Observable<UserAndEvents> combined = Observable.Zip(userObservable, eventsObservable, new Func2<JsonObject, JsonArray, UserAndEvents>() {
  @Override
  public UserAndEvents call(JsonObject jsonObject, JsonArray jsonElements) {
    return new UserAndEvents(jsonObject, jsonElements);
  }
});

最後に、新しい結合Observableでsubscribeメソッドを呼び出します。

combined.subscribe(new Subscriber<UserAndEvents>() {
          ...
          @Override
          public void onNext(UserAndEvents o) {
            // You can access the results of the 
            // two observabes via the POJO now
          }
        });

ネットワークコールの終了をスレッドなどで待機する必要はありません。 RxJavaは、Zip()ですべてを実行しました。私の答えがお役に立てば幸いです。

2
Jyubin Patel

RxJava2で同様の問題を解決しました。 Api 2のリクエストを並行して実行すると、作業が少しスピードアップします。

private InformationRepository informationRepository;

//init....

public Single<List<FullInformation>> getFullInformation() {
    return informationRepository.getInformationList()
            .subscribeOn(Schedulers.io())//I usually write subscribeOn() in the repository, here - for clarity
            .flatMapObservable(Observable::fromIterable)
            .flatMapSingle(this::getFullInformation)
            .collect(ArrayList::new, List::add);

}

private Single<FullInformation> getFullInformation(Information information) {
    return informationRepository.getExtendedInformation(information)
            .map(extendedInformation -> new FullInformation(information, extendedInformation))
            .subscribeOn(Schedulers.io());//execute requests in parallel
}

InformationRepository-インターフェースのみ。その実装は、私たちにとって興味深いものではありません。

public interface InformationRepository {

    Single<List<Information>> getInformationList();

    Single<ExtendedInformation> getExtendedInformation(Information information);
}

FullInformation-結果のコンテナー。

public class FullInformation {

    private Information information;
    private ExtendedInformation extendedInformation;

    public FullInformation(Information information, ExtendedInformation extendedInformation) {
        this.information = information;
        this.extendedInformation = extendedInformation;
    }
}
0
Anrimian

Observable.Zip()演算子を使用してみてください。ストリームを続行する前に、両方のApi呼び出しが完了するまで待機します。その後、flatMap()を呼び出して、いくつかのロジックを挿入できます。

http://reactivex.io/documentation/operators/Zip.html

0
Fraudlic