使用RxJava缓存网络调用一段时间

时间:2017-04-03 18:38:41

标签: android caching retrofit2 rx-java2 replay

我正在使用Retorfit + RxJava2制作网络,我希望将响应缓存30秒。 30秒间隔后进行的任何调用都应该从服务器获得最新结果。我尝试使用Replay运算符执行此操作,但每次调用subscribe时它仍会进行网络调用。我不是RxJava的专家,所以也许我对使用Replay进行缓存的理解是错误的。

public Observable<Name> getName() {
        return retrofitBuilder.getName()
                .subscribeOn(Schedulers.io())
                .replay(30, TimeUnit.SECONDS,Schedulers.io())
                .autoConnect();
    }

我正在调用上面的代码:

 service.getName()
        .subscribe(new Consumer<Name>()
            {
                @Override
                public void accept(Name name) throws Exception
                {
                    Log.d("getName", "Name: " + name.toString());
                }
            }
            , new Consumer<Throwable>()
            {
                @Override
                public void accept(Throwable throwable) throws Exception
                {
                    Log.d("getName", throwable.getMessage());
                }
            });

更新:如果我没有清楚地解释我的问题,我的道歉。我想要的是缓存一个特定的请求,而不是在HttpClient级别缓存它,它将缓存策略应用于通过它进行的所有请求。最后,我想在需要时为不同的请求定义不同的缓存过期。并非我的所有请求都需要缓存一小段时间。我想知道我是否能做到这一点。

感谢您的帮助。

2 个答案:

答案 0 :(得分:2)

你的方法有两个问题:

  1. 正如@drhr所提到的那样,每当您致电Observable并创建新的service.getName()实例时,您都会创建新的Observable,您应该保留相同的重播实例并在每次调用service.getName()时向同一实例外的调用者提供。
  2. 即使你将返回相同的实例,replay 30秒,将重放源Observable在过去30秒内发出的序列,这意味着在缓存到期时间后,你将得不到任何结果因为您的请求超过30秒前发生。它并不意味着Observable将在此期间后自动重启。
  3. 为了缓存特定时间段,您基本上需要在缓存期后使缓存的响应无效,并在此期间后执行新的请求,这意味着您应该控制您的订阅,并在那里进行。
    你可以用这样的东西来实现它:

    public class CachedRequest<T> {
    
        private final AtomicBoolean expired = new AtomicBoolean(true);
        private final Observable<T> source;
        private final long cacheExpirationInterval;
        private final TimeUnit cacheExpirationUnit;
        private Observable<T> current;
    
        public CachedRequest(Observable<T> o, long cacheExpirationInterval,
                             TimeUnit cacheExpirationUnit) {
            source = o;
            current = o;
            this.cacheExpirationInterval = cacheExpirationInterval;
            this.cacheExpirationUnit = cacheExpirationUnit;
        }
    
        private Observable<T> getCachedObservable() {
            return Observable.defer(() -> {
                if (expired.compareAndSet(true, false)) {
                    current = source.cache();
                    Observable.timer(cacheExpirationInterval, cacheExpirationUnit)                          
                            .subscribe(aLong -> expired.set(true));
                }
                return current;
            });
        }
    }
    

    使用defer,您可以根据缓存过期状态返回正确的Observable,因此每次在缓存过期内发生的订阅都会被缓存Observable(使用cache()) - 意味着请求将是只执行一次。 在缓存过期后,附加订阅将触发新请求,并将设置新计时器以重置缓存过期。

答案 1 :(得分:1)

尝试查看okhttp拦截器。

添加CacheInterceptor:

public class CacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = chain.proceed(chain.request());

        CacheControl cacheControl = new CacheControl.Builder()
                .maxAge(30, TimeUnit.SECONDS)
                .build();

        return response.newBuilder()
                .removeHeader("Pragma")
                .removeHeader("Cache-Control")
                .header("Cache-Control", cacheControl.toString())
                .build();
    }
}

将其添加并缓存到您的OkHttp客户端,如下所示:

File httpCacheDirectory = new File(context.getCacheDir(), "http-cache");
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(httpCacheDirectory, cacheSize);

OkHttpClient httpClient = new OkHttpClient.Builder()
                               .addNetworkInterceptor(new CacheInterceptor())
                               .cache(cache)
                               .build();