关于反应式编程:如何使用/控制 RxJava Observable.cache

How to use/control RxJava Observable.cache

我正在尝试使用 RxJava 缓存机制 (RxJava2),但我似乎无法理解它是如何工作的,或者我如何控制缓存的内容,因为有 cache 运算符。

我想在发出新数据之前用一些条件验证缓存的数据。

例如

1
2
3
4
5
someObservable.
repeat().
filter { it.age < maxAge }.
map(it.name).
cache()

我如何检查和过滤缓存值,如果成功则发出它,否则我将请求一个新值。

由于值会定期更改,因此我需要验证缓存是否仍然有效,然后才能请求新缓存。

还有 ObservableCache<T> 类,但我找不到任何使用它的资源。

任何帮助将不胜感激。谢谢。


这不是重放/缓存的工作方式。请先阅读#replay/#cache 文档。

重播

这个操作符返回一个 ConnectableObservable,它有一些方法 (#refCount/ #connect/ #autoConnect) 用于连接到源。

当#replay 被应用而没有过载时,源订阅被多播并且所有发出的值sind connection 将被重播。源订阅是惰性的,可以通过#refCount/#connect/#autoConnect 连接到源。

Returns a ConnectableObservable that shares a single subscription to the underlying ObservableSource that will replay all of its items and notifications to any future Observer.

在没有任何连接方法的情况下应用#relay (#refCount/ #connect/ #autoConnect) 不会在订阅上发出任何值

A Connectable ObservableSource resembles an ordinary ObservableSource, except that it does not begin emitting items when it is subscribed to, but only when its connect method is called.

重播(1)#autoConnect(-1) / #refCount(1) / #connect

应用 replay(1) 将缓存最后一个值,并将在每个订阅上发出缓存的值。 #autoConnect 将立即打开连接并保持打开状态,直到发生终端事件(onComplete、onError)。 #refCount 是 smiular,但会在所有订阅者消失时断开与源的连接。 #connect 运算符可以在您需要等待时使用,当所有对 observable 的订阅都已完成时,以免错过值。

用法

#replay(1) -- 大部分应该用在 observable 的末尾。

1
2
3
4
5
sourcObs.
  .filter()
  .map()
  .replay(bufferSize)
  .refCount(connectWhenXSubsciberSubscribed)

警告

在没有缓冲区限制或到期日期的情况下应用#replay 将导致内存泄漏,当您观察到无限时

缓存 / cacheWithInitialCapacity

运算符类似于带有 autoConnect(1) 的 #replay。运算符将缓存每个值并在每个订阅上重播。

The operator subscribes only when the first downstream subscriber subscribes and maintains a single subscription towards this ObservableSource. In contrast, the operator family of replay() that return a ConnectableObservable require an explicit call to ConnectableObservable.connect().
Note: You sacrifice the ability to dispose the origin when you use the cache Observer so be careful not to use this Observer on ObservableSources that emit an infinite or very large number of items that will use up memory. A possible workaround is to apply takeUntil with a predicate or another source before (and perhaps after) the application of cache().

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    @Test
    fun skfdsfkds() {
        val create = PublishSubject.create<Int>()

        val cacheWithInitialCapacity = create
            .cacheWithInitialCapacity(1)

        cacheWithInitialCapacity.subscribe()

        create.onNext(1)
        create.onNext(2)
        create.onNext(3)

        cacheWithInitialCapacity.test().assertValues(1, 2, 3)
        cacheWithInitialCapacity.test().assertValues(1, 2, 3)
    }

用法

使用缓存操作符,当你无法控制连接阶段时

This is useful when you want an ObservableSource to cache responses and you can't control the subscribe/dispose behavior of all the Observers.

警告

与 replay() 一样,缓存是无限的,可能会导致内存泄漏。

Note: The capacity hint is not an upper bound on cache size. For that, consider replay(int) in combination with ConnectableObservable.autoConnect() or similar.

进一步阅读

https://blog.danlew.net/2018/09/25/connectable-observables-so-hot-right-now/

https://blog.danlew.net/2016/06/13/multicasting-in-rxjava/


如果您的事件源 (Observable) 是昂贵的操作,例如从数据库中读取,则不应使用 Subject 来观察事件,因为这将为每个订阅者重复昂贵的操作。由于"OutOfMemory"异常,缓存也可能存在无限流的风险。更合适的解决方案可能是ConnectableObservable,它只执行一次源操作,并将更新后的值广播给所有订阅者。

这是一个代码示例。我没有费心创建无限周期流或包含错误处理以保持示例简单。让我知道它是否满足您的需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class RxJavaTest {

    private final int maxValue = 50;

    private final ConnectableObservable<Integer> source =
            Observable.<Integer>create(
                subscriber -> {
                    log("Starting Event Source");
                    subscriber.onNext(readFromDatabase());
                    subscriber.onNext(readFromDatabase());
                    subscriber.onNext(readFromDatabase());
                    subscriber.onComplete();
                    log("Event Source Terminated");
                })
                .subscribeOn(Schedulers.io())
                .filter(value -> value < maxValue)
                .publish();

    void run() throws InterruptedException {
        log("Starting Application");

        log("Subscribing");
        source.subscribe(value -> log("Subscriber 1:" + value));
        source.subscribe(value -> log("Subscriber 2:" + value));

        log("Connecting");
        source.connect();

        // Add sleep to give event source enough time to complete
        log("Application Terminated");
        sleep(4000);
    }

    private Integer readFromDatabase() throws InterruptedException {
        // Emulate long database read time
        log("Reading data from database...");
        sleep(1000);

        int randomValue = new Random().nextInt(2 * maxValue) + 1;
        log(String.format("Read value: %d", randomValue));
        return randomValue;
    }

    private static void log(Object message) {
        System.out.println(
                Thread.currentThread().getName() +">>" + message
        );
    }
}

这是输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
main >> Starting Application
main >> Subscribing
main >> Connecting
main >> Application Terminated
RxCachedThreadScheduler-1 >> Starting Event Source
RxCachedThreadScheduler-1 >> Reading data from database...
RxCachedThreadScheduler-1 >> Read value: 88
RxCachedThreadScheduler-1 >> Reading data from database...
RxCachedThreadScheduler-1 >> Read value: 42
RxCachedThreadScheduler-1 >> Subscriber 1: 42
RxCachedThreadScheduler-1 >> Subscriber 2: 42
RxCachedThreadScheduler-1 >> Reading data from database...
RxCachedThreadScheduler-1 >> Read value: 37
RxCachedThreadScheduler-1 >> Subscriber 1: 37
RxCachedThreadScheduler-1 >> Subscriber 2: 37
RxCachedThreadScheduler-1 >> Event Source Terminated.

请注意以下几点:

  • 事件仅在源上调用 connect() 时才开始触发,而不是在观察者订阅源时触发。
  • 每次事件更新仅进行一次数据库调用
  • 过滤后的值不会发送给订阅者
  • 所有订阅者都在同一个线程中执行
  • 由于并发,应用程序在处理事件之前终止。通常,您的应用程序将在事件循环中运行,因此您的应用程序将在缓慢操作期间保持响应。