RxJava:找出BehaviorSubject是否是重复值

时间:2015-05-11 23:03:10

标签: java android reactive-programming rx-java

我正在创建一个Android界面,显示从网络中获取的一些数据。我想让它显示最新的可用数据,并且永远不会为空(除非根本没有提取任何数据)所以我使用BehaviorSubject为订阅者(我的UI)提供最新的可用信息,同时刷新它更新它的背景。

这很有效,但是由于我的UI中的另一个要求,我现在必须知道发布的结果是否是从网络中获得的。 (换句话说,我需要知道发布的结果是否是BehaviorSubject的已保存项目。)

我怎样才能做到这一点?如果我需要将它拆分为多个Observable,那很好,只要我能够获得BehaviorSubject的缓存行为(获取最后可用的结果),同时还能够判断返回的结果是来自缓存还是不。我能想到的一个hacky方法是检查响应的时间戳是否相对较快,但这确实很草率,而我宁愿找到一种方法来使用RxJava。

5 个答案:

答案 0 :(得分:7)

正如您在问题中提到的,这可以通过多个Observable来实现。实质上,您有两个Observable:“可以观察到新的响应”,“可以观察缓存的响应”。如果可以“观察”某些东西,您可以将其表达为Observable。我们将第一个命名为original,将第二个命名为replayed

请参阅此JSBin(JavaScript,但这些概念可以直接翻译为Java。据我所知,目前还没有JavaBin用于这些目的。)

var original = Rx.Observable.interval(1000)
  .map(function (x) { return {value: x, from: 'original'}; })
  .take(4)
  .publish().refCount();

var replayed = original
  .map(function (x) { return {value: x.value, from: 'replayed'}; })
  .replay(null, 1).refCount();

var merged = Rx.Observable.merge(original, replayed)
  .replay(null, 1).refCount()
  .distinctUntilChanged(function (obj) { return obj.value; });

console.log('subscribe 1st');
merged.subscribe(function (x) { 
  console.log('subscriber1: value ' + x.value + ', from: ' + x.from);
});

setTimeout(function () {
  console.log('    subscribe 2nd');
  merged.subscribe(function (x) {
    console.log('    subscriber2: value ' + x.value + ', from: ' + x.from);
  });
}, 2500);

这里的总体思路是:使用字段from注释事件来指示其来源。如果它是original,则是一个新的回复。如果它是replayed,则它是缓存的响应。可观察的original只会发出from: 'original',而观察replayed只会发出from: 'replayed'。在Java中,我们需要更多样板文件,因为您需要创建一个类来表示这些带注释的事件。否则,可以在RxJava中找到RxJS中的相同运算符。

原始的Observable是publish().refCount(),因为我们只希望与所有观察者共享此流的一个实例。实际上在RxJS和Rx.NET中,share()publish().refCount()的别名。

重播的Observable是replay(1).refCount(),因为它也像原始的一样被共享,但是replay(1)为我们提供了缓存行为。

merged Observable包含原始和重播,这是您应该向所有订阅者公开的内容。由于replayed会在original发生时立即发出,因此我们对事件的值使用distinctUntilChanged来忽略即时连续。我们replay(1).refCount() 合并的原因是因为我们希望原始和重放的合并也是所有观察者之间共享的流的一个共享实例。我们会为此目的使用publish().refCount(),但我们不能失去replayed包含的重播效果,因此它是replay(1).refCount(),而不是publish().refCount()

答案 1 :(得分:1)

Distinct不支持你的案子吗? BehaviorSubject仅在订阅后重复最新元素。

答案 2 :(得分:1)

我相信你想要的是这样的:

private final BehaviorSubject<T> fetched = BehaviorSubject.create();
private final Observable<FirstTime<T>> _fetched = fetched.lift(new Observable.Operator<FirstTime<T>, T>() {
    private AtomicReference<T> last = new AtomicReference<>();
    @Override
    public Subscriber<? super T> call(Subscriber<? super FirstTime<T>> child) {
        return new Subscriber<T>(child) {
            @Override
            public void onCompleted() {
                child.onCompleted();
            }

            @Override
            public void onError(Throwable e) {
                child.onError(e);
            }

            @Override
            public void onNext(T t) {
                if (!Objects.equals(t, last.getAndSet(t))) {
                    child.onNext(FirstTime.yes(t));
                } else {
                    child.onNext(FirstTime.no(t));
                }
            }
        };
    }
});

public Observable<FirstTime<T>> getObservable() {
    return _fetched;
}

public static class FirstTime<T> {
    final boolean isItTheFirstTime;
    final T value;

    public FirstTime(boolean isItTheFirstTime, T value) {
        this.isItTheFirstTime = isItTheFirstTime;
        this.value = value;
    }

    public boolean isItTheFirstTime() {
        return isItTheFirstTime;
    }

    public T getValue() {
        return value;
    }

    public static <T> FirstTime<T> yes(T value) {
        return new FirstTime<>(true, value);
    }

    public static <T> FirstTime<T> no(T value) {
        return new FirstTime<>(false, value);
    }
}

包装类FirstTime有一个布尔值,可用于查看Observable的任何订阅者之前是否已经看过它。

希望有所帮助。

答案 3 :(得分:0)

将BehaviorSubject对象的信息存储在具有良好查找的数据结构中,例如Dictionnary。每个值都是一个键,值将是迭代次数。

那么,当你看一个特定的键时,如果你的词典已经包含它并且它的值已经是一个,那么你知道一个值是一个重复的值。

答案 4 :(得分:0)

我不确定你想要达到的目标。可能你只是想拥有最新的&#34;最新的&#34;数据和第二个来源告诉您何时刷新数据?

    BehaviorSubject<Integer> dataSubject = BehaviorSubject.create(42); // initial value, "never empty"

    Observable<String> refreshedIndicator = dataSubject.map(data -> "Refreshed!");
    refreshedIndicator.subscribe(System.out::println);

    Observable<Integer> latestActualData = dataSubject.distinctUntilChanged();
    latestActualData.subscribe( data -> System.out.println( "Got new data: " + data));

    // simulation of background activity:
    Observable.interval(1, TimeUnit.SECONDS)
            .limit(100)
            .toBlocking()
            .subscribe(aLong -> dataSubject.onNext(ThreadLocalRandom.current().nextInt(2)));

输出:

Refreshed!
Got new data: 42
Refreshed!
Got new data: 0
Refreshed!
Refreshed!
Refreshed!
Got new data: 1
Refreshed!
Got new data: 0
Refreshed!
Got new data: 1