RxJava:从缓存读取,直到时间戳过期

时间:2017-12-20 05:50:11

标签: java android rx-java reactive-programming

我有以下场景:我有一个名为Foo的对象,我的应用程序感兴趣。这个Foo对象来自远程服务,我想在本地缓存此实例并保持使用它直到一定时间过去。

到目前为止,我已尝试创建一个FooState类,其中包含Foo的实例以及一个时间戳,指示以毫秒为单位提取Foo的时间:

public class FooState {

    private Foo foo;
    private long timestamp;

    /* Constructor and getters */

}

现在,到目前为止,我已经提出了使用concat的代码:

public Observable<Foo> foo() {
    return Observable.concat(local(), remote())
               .takeFirst(fooState -> fooState.getTimestamp() >= System.currentTimeMillis())
               .map(fooState -> fooState.getFoo())
               .defaultIfEmpty(new Foo());

}

private Observable<FooState> local() {
    return Observable.just(cache.hasFooState() ? cache.getFooState() : new FooState(null, 0));
}

private Observable<FooState> remote() {
    return api.getFoo()
              .map(foo -> new FooState(foo, System.currentTimeMillis() + ONE_DAY_MILLIS)
              .doOnNext(fooState -> {
                   cache.save(fooState);
              });
}

基本上,如果有缓存值,我想只要时间戳未过期就使用它。如果时间戳已过期或没有缓存值,我想从远程服务获取并缓存结果。

是否有更简洁的方法来实现此用例?我是RxJava的新手,我想知道是否有任何Rx-gurus知道更好的方法来处理这种情况。

2 个答案:

答案 0 :(得分:0)

您还可以使用内置的 .timestamp()运算符和 Timestamped&lt; T&gt; 类。

    BehaviorSubject<Timestamped<Foo>> subject = BehaviorSubject.create();

    Observable<Foo> serviceCall() {
        return subject.filter(new Func1<Timestamped<Foo>, Boolean>() {
            @Override
            public Boolean call(Timestamped<Foo> tsFoo) {
                return tsFoo.getTimestampMillis() < expiry;
            }
        }).switchIfEmpty(serviceCall().timestamp().doOnNext(new Action1<Timestamped<Foo>>() {
            @Override
            public void call(Timestamped<Foo> tsFoo) {
                subject.onNext(tsFoo);
            }
        })
        .map(new Func1<Timestamped<Object>, Foo>() {
            @Override
            public Foo call(Timestamped<Object> tsFoo) {
                return tsFoo.getValue();
            }
        });
    }

答案 1 :(得分:0)

我想出了类似于concat的方法,因为它似乎最适合这个用例,但是基于Maybe(RxJava 2)构建我的解决方案,它不会发出如果没有缓存或缓存条目已过期,则从本地获取任何内容。

public class RxJavaUnitTestJava {
    public class Foo extends Object {
        String source = "n/a";
    }

    public class FooState {
        private Foo foo;
        private long timestamp;

        public FooState(Foo foo, long timestamp) {
            this.foo = foo;
            this.timestamp = timestamp;
        }
    }

    private static long EXPIRATION = 0l;
    private FooState cachedFooState = null;

    private Maybe<Foo> local() {
        return Maybe.fromCallable(() -> {
            System.out.println("checking local");

            if (cachedFooState != null && cachedFooState.timestamp + EXPIRATION > System.currentTimeMillis()) {
                System.out.println("taking unexpired local");
                cachedFooState.foo.source = "local"; // mark source of this foo

                return cachedFooState.foo;
            } else {
                System.out.println("not taking local");
                return null;
            }
        });
    } 

    private Maybe<Foo> remote() {
        return Maybe.fromCallable(() -> {
            System.out.println("checking remote");

            Foo foo = new Foo();
            foo.source = "remote"; // mark source of this foo
            FooState fooState = new FooState(foo, System.currentTimeMillis());
            cachedFooState = fooState;

            return fooState.foo;
        });
    }

    private Observable<Foo> foo() {
        return Maybe.concat(local(), remote())
                .take(1)
                .toObservable();
    }

    @Test
    public void testNoCachedLocal() {
        cachedFooState = null;

        foo()
                .doOnNext(foo -> System.out.println("doOnNext: foo.source: " + foo.source))
                .doOnComplete(() -> System.out.println("onComplete"))
                .test()
                .assertValueCount(1)
                .assertComplete();
    }

    @Test
    public void testExpiredLocal() {
        cachedFooState = new FooState(new Foo(), System.currentTimeMillis());
        EXPIRATION = 0l;

        foo()
                .doOnNext(foo -> System.out.println("doOnNext: foo.source: " + foo.source))
                .doOnComplete(() -> System.out.println("onComplete"))
                .test()
                .assertValueCount(1)
                .assertComplete();
    }

    @Test
    public void testUnExpiredLocal() {
        cachedFooState = new FooState(new Foo(), System.currentTimeMillis());
        EXPIRATION = TimeUnit.SECONDS.toMillis(30);

        foo()
                .doOnNext(foo -> System.out.println("doOnNext: foo.source: " + foo.source))
                .doOnComplete(() -> System.out.println("onComplete"))
                .test()
                .assertValueCount(1)
                .assertComplete();
    }
}