RxJava组合请求序列

时间:2017-12-04 20:39:18

标签: java android rx-java reactive-programming rx-android

问题

我有两个Apis。 Api 1给了我一个项目列表,Api 2给了我更多关于我从Api获得的每个项目的详细信息。到目前为止,我解决它的方式导致了糟糕的性能。

问题

在Retrofit和RxJava的帮助下,快速,快速地解决了这个问题。

我的方法

在片刻我的解决方案看起来像这样:

步骤1:Retrofit从Api 1执行Single<ArrayList<Information>>

步骤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);

    }

5 个答案:

答案 0 :(得分:20)

tl; dr 使用concatMapEagerflatMap并异步或在调度程序上执行子调用。

长篇故事

我不是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()))
    

原因是flatMap将订阅由api2.extendedInfo(...)创建的每个observable,并将它们合并到生成的observable中。因此,结果将按照收到的顺序显示。

如果改装客户端 设置为异步或设置为在调度程序上运行,则可以设置一个:

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

这个结构几乎与之前的一个execpts相同,它表示 local ,每个api2.extendedInfo应该运行在哪个调度程序上。

可以调整maxConcurrency的{​​{1}}参数来控制您想要同时执行的请求数。虽然我对此问题保持谨慎,但您不希望同时运行所有查询。通常默认flatMap足够好(maxConcurrency)。

现在,如果原始查询的顺序很重要128通常是运算符按顺序执行与concatMap相同的操作,但顺序执行,如果代码需要等待执行所有子查询,则结果会很慢。然而,解决方案是flatMap更进一步,这个将按顺序订阅observable,并根据需要缓冲结果。

假设改造客户端是异步或在特定调度程序上运行:

concatMapEager

或者如果必须在本地设置调度程序:

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

也可以在此运算符中调整并发性。

此外,如果Api返回api1.items(queryParam) .flatMap(itemList -> Observable.fromIterable(itemList))) .concatMapEager(item -> api2.extendedInfo(item.id()).subscribeOn(Schedulers.io())) .subscribe(...) ,则可以在RxJava 2.1.7中使用目前仍处于测试阶段的Flowable。但结果并不合适,我不知道一种方式(但是?)订购它们而不进行排序。

.parallel

答案 1 :(得分:2)

flatMap运算符旨在满足这些类型的工作流程。

我将通过一个简单的五步示例勾勒出广泛的笔画。希望您可以轻松地在代码中重建相同的原则:

@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)。

我希望有所帮助。

答案 2 :(得分:2)

Check below it's working.

Say you have multiple network calls you need to make–cals to get Github user information and Github user events for example.

And you want to wait for each to return before updating the UI. RxJava can help you here. Let’s first define our Retrofit object to access Github’s API, then setup two observables for the two network requests call.

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());

Used Interface for it like below:

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);
}

After we use RxJava’s zip method to combine our two Observables and wait for them to complete before creating a new 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);
  }
});

Finally let’s call the subscribe method on our new combined Observable:

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

No more waiting in threads etc for network calls to finish. RxJava has done all that for you in zip(). hope my answer helps you.

答案 3 :(得分:0)

我用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;
    }
}

答案 4 :(得分:0)

尝试使用Observable.zip()运算符。它将等待两个Api呼叫完成后再继续流。然后,您可以通过之后调用flatMap()来插入一些逻辑。

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