如何在RxJava 2中组合动态数量的Observable?

时间:2018-03-01 12:21:14

标签: java rx-java2

我遇到了一个非常奇怪的问题并请求你的帮助。

总体设置如下:我有一堆数据提供程序可以在运行时发生或消失。这些提供商提供了 - 大惊喜 - 数据,建模为io.reactivex.Observable。 (确切地说:作为BehaviorSubject,记住新订阅者的最新数据。)

现在,我需要结合所有当前数据提供者的数据,以便获得一个新的“主”Observable,当任何数据提供者的可观察变化或新的提供者出现时(或旧的提供者消失),它会得到更新。

到目前为止,这听起来像是合并,但对于主Observable,我需要所有提供商的数据,在任何变化时,每个提供商的相应最后状态。这种组合适用于使用Observable.combineLatest预先知道的非动态提供者。

但是当我将该方法嵌入到动态上下文中时,会出现问题,并且会监听添加或删除的提供程序。 然后更新其中一个提供程序的Observable,不仅会触发一次预期的更新,还会触发多次更新,其中一些更新只包含部分数据。

看看以下(自包含,使用RxJava 2.1.9)示例,我应该澄清我的问题。注释以println()完成,因此生成的输出也是可读的。第一部分是静态的,无需添加或删除提供程序,并按预期工作。第二部分是奇怪的事情......

我很感激任何进一步的想法或帮助来解决这个问题 - 谢谢!

import io.reactivex.Observable;
import io.reactivex.functions.Function;
import io.reactivex.subjects.BehaviorSubject;
import io.reactivex.subjects.Subject;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class CombineLatestWithDynamicSources {

    static final Function<Object[], List<String>> STRING_COMBINER = objects -> Arrays.stream(objects)
                                                                                     .map(Object::toString)
                                                                                     .collect(Collectors.toList());

    public static void main(String[] args) {

        System.out.println("*** STATIC ***");
        staticCombineWorksAsExpected();

        System.out.println("\n*** DYNAMIC ***");
        dynamicCombineBehavesWeird();

    }

    static void staticCombineWorksAsExpected() {

        Subject<String> subjectA = BehaviorSubject.createDefault("A.1");
        Subject<String> subjectB = BehaviorSubject.createDefault("B.1");

        List<Subject<String>> subjects = Arrays.asList(subjectA, subjectB); // potentially more...

        Observable<List<String>> combined = Observable.combineLatest(subjects, STRING_COMBINER);

        System.out.println("initial values:");
        combined.subscribe(strings -> System.out.println(">> Combined: " + strings));

        System.out.println("updating A:");
        subjectA.onNext("A.2");

        System.out.println("updating B:");
        subjectB.onNext("B.2");

        System.out.println("\n... works as expected, but adding subjects isn't possible in this setting.");
    }

    static void dynamicCombineBehavesWeird() {

        List<Subject<String>> subjectsList = new ArrayList<>();
        Subject<List<Subject<String>>> subjectsObservable = BehaviorSubject.createDefault(subjectsList);

        System.out.println("subjects are initially empty:");
        subjectsObservable.subscribe(subjects -> System.out.println(">> Subjects: " + subjects));

        Observable<List<String>> combined = subjectsObservable.flatMap(
                subjects -> Observable.combineLatest(subjects, STRING_COMBINER));

        combined.subscribe(strings -> System.out.println(">> Combined: " + strings));

        System.out.println("add Subject A, providing default value 'A.1' - as expected:");
        Subject<String> subjectA = BehaviorSubject.createDefault("A.1");
        subjectsList.add(subjectA);
        subjectsObservable.onNext(subjectsList);

        System.out.println("updating A - also as expected:");
        subjectA.onNext("A.2");

        System.out.println("add Subject B, providing default value 'B.1' - as expected, now both subject's last values show up:");
        Subject<String> subjectB = BehaviorSubject.createDefault("B.1");
        subjectsList.add(subjectB);
        subjectsObservable.onNext(subjectsList);

        System.out.println("updating A again - I'd expect the second result only! Why is there '[A.3]' popping up before the expected result?!");
        subjectA.onNext("A.3");

        System.out.println("This doesn't happen on updating B...:");
        subjectB.onNext("B.2");

        System.out.println("digging deeper, add Subject C, providing default value 'C.1' - as expected again:");
        Subject<String> subjectC = BehaviorSubject.createDefault("C.1");
        subjectsList.add(subjectC);
        subjectsObservable.onNext(subjectsList);

        System.out.println("Now update A - three results pop up, only the last is expected!");
        subjectA.onNext("A.4");

        System.out.println("update B, which now emits also two results - last expected only:");
        subjectB.onNext("B.3");

        System.out.println("update C works as expected:");
        subjectC.onNext("C.2");

        System.out.println("\n... huh? Seems on updating the first source, the combined results gets computed for the first, " + "then for the first and second, then for first, second and third (and so on?) source...");

    }

}

...产生以下输出:

*** STATIC ***
initial values:
>> Combined: [A.1, B.1]
updating A:
>> Combined: [A.2, B.1]
updating B:
>> Combined: [A.2, B.2]

... works as expected, but adding subjects isn't possible in this setting.

*** DYNAMIC ***
subjects are initially empty:
>> Subjects: []
add Subject A, providing default value 'A.1' - as expected:
>> Subjects: [io.reactivex.subjects.BehaviorSubject@4157f54e]
>> Combined: [A.1]
updating A - also as expected:
>> Combined: [A.2]
add Subject B, providing default value 'B.1' - as expected, now both subject's last values show up:
>> Subjects: [io.reactivex.subjects.BehaviorSubject@4157f54e, io.reactivex.subjects.BehaviorSubject@90f6bfd]
>> Combined: [A.2, B.1]
updating A again - I'd expect the second result only! Why is there '[A.3]' popping up before the expected result?!
>> Combined: [A.3]
>> Combined: [A.3, B.1]
This doesn't happen on updating B...:
>> Combined: [A.3, B.2]
digging deeper, add Subject C, providing default value 'C.1' - as expected again:
>> Subjects: [io.reactivex.subjects.BehaviorSubject@4157f54e, io.reactivex.subjects.BehaviorSubject@90f6bfd, io.reactivex.subjects.BehaviorSubject@47f6473]
>> Combined: [A.3, B.2, C.1]
Now update A - three results pop up, only the last is expected!
>> Combined: [A.4]
>> Combined: [A.4, B.2]
>> Combined: [A.4, B.2, C.1]
update B, which now emits also two results - last expected only:
>> Combined: [A.4, B.3]
>> Combined: [A.4, B.3, C.1]
update C works as expected:
>> Combined: [A.4, B.3, C.2]

... huh? Seems on updating the first source, the combined results gets computed for the first, then for the first and second, then for first, second and third (and so on?) source...

2 个答案:

答案 0 :(得分:0)

我的结合问题使我在这里问了好几天 - 但即使在我的问题之后,它也没有让我离开......现在终于,我可以继续挖掘它,我有一个解决方案。问题在于没有处理以前订阅里面的 flatmap。糟!

首先,我将链条拆开以了解运动部件:

List<Subject<String>> subjectsList = new ArrayList<>();
Subject<List<Subject<String>>> subjectsObservable = BehaviorSubject.createDefault(subjectsList);

final Disposable[] disposable = new Disposable[]{Disposables.empty()};

// combined now is a subject to push updates into
Subject<List<String>> combined = BehaviorSubject.createDefault(Collections.emptyList());

subjectsObservable.subscribe(subjects -> {
    Observable<List<String>> combSubj = Observable.combineLatest(subjects, STRING_COMBINER);

    // here's the key: I remember the previous subscription and dispose it on update.
    disposable[0].dispose();
    disposable[0] = combSubj.subscribe(strings -> combined.onNext(strings));
});

combined.subscribe(strings -> System.out.println(">> Combined: " + strings));

最后,有点整洁,更接近我原来的(不工作)解决方案:

// remember the disposables, start with an empty to avoid NullPointerExceptions
Disposable[] prevDisposable = new Disposable[]{Disposables.empty()};
Observable<List<String>> combined = subjectsObservable.flatMap(
        subjects -> Observable.combineLatest(subjects, STRING_COMBINER)
                              // dispose the previous subscription to combineLatest's Observable and remember the new one
                              .doOnSubscribe(disposable -> {
                                    prevDisposable[0].dispose();
                                    prevDisposable[0] = disposable;
                              })
        );

combined.subscribe(strings -> System.out.println(">> Combined: " + strings));

答案 1 :(得分:0)

从那以后的一段时间-现在我知道了...;-)

简单的解决方案-如此简单...:m

subjectsObservable.switchMap(subjects -> Observable.combineLatest(subjects, STRING_COMBINER))
                  .subscribe(strings -> System.out.println(">> Combined: " + strings));