OKHttp可以在脱机时使用caching数据

我正在尝试使用Retrofit&OKHttp来cachingHTTP响应。 我遵循这个要点 ,结束了这个代码:

File httpCacheDirectory = new File(context.getCacheDir(), "responses"); HttpResponseCache httpResponseCache = null; try { httpResponseCache = new HttpResponseCache(httpCacheDirectory, 10 * 1024 * 1024); } catch (IOException e) { Log.e("Retrofit", "Could not create http cache", e); } OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.setResponseCache(httpResponseCache); api = new RestAdapter.Builder() .setEndpoint(API_URL) .setLogLevel(RestAdapter.LogLevel.FULL) .setClient(new OkClient(okHttpClient)) .build() .create(MyApi.class); 

这是带有Cache-Control头的MyApi

 public interface MyApi { @Headers("Cache-Control: public, max-age=640000, s-maxage=640000 , max-stale=2419200") @GET("/api/v1/person/1/") void requestPerson( Callback<Person> callback ); 

首先,我请求在线并检查caching文件。 正确的JSON响应和标题在那里。 但是当我尝试请求离线时,我总是得到RetrofitError UnknownHostException 。 还有什么我应该做的,使Retrofit从caching中读取响应?

编辑:由于OKHttp 2.0.x HttpResponseCacheCachesetResponseCachesetCache

编辑为改造2.x:

OkHttp拦截器是在脱机状态下访问caching的正确方法:

1)创build拦截器:

 private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Response originalResponse = chain.proceed(chain.request()); if (Utils.isNetworkAvailable(context)) { int maxAge = 60; // read from cache for 1 minute return originalResponse.newBuilder() .header("Cache-Control", "public, max-age=" + maxAge) .build(); } else { int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale return originalResponse.newBuilder() .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) .build(); } } 

2)安装客户端:

 OkHttpClient client = new OkHttpClient(); client.networkInterceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR); //setup cache File httpCacheDirectory = new File(context.getCacheDir(), "responses"); int cacheSize = 10 * 1024 * 1024; // 10 MiB Cache cache = new Cache(httpCacheDirectory, cacheSize); //add cache to the client client.setCache(cache); 

3)添加客户端进行改造

 Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); 

另外检查@ kosiara – Bartosz Kosarzycki的答案 。 您可能需要从响应中删除一些标题。


OKHttp 2.0.x(检查原始答案):

由于OKHttp 2.0.x HttpResponseCacheCachesetResponseCachesetCache 。 所以你应该像这样设置setCache

  File httpCacheDirectory = new File(context.getCacheDir(), "responses"); Cache cache = null; try { cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024); } catch (IOException e) { Log.e("OKHttp", "Could not create http cache", e); } OkHttpClient okHttpClient = new OkHttpClient(); if (cache != null) { okHttpClient.setCache(cache); } String hostURL = context.getString(R.string.host_url); api = new RestAdapter.Builder() .setEndpoint(hostURL) .setClient(new OkClient(okHttpClient)) .setRequestInterceptor(/*rest of the answer here */) .build() .create(MyApi.class); 

原始答案:

事实certificate,服务器响应必须具有Cache-Control: public才能使OkClient从caching中读取数据。

另外如果你想从networking请求可用时,你应该添加Cache-Control: max-age=0请求标题。 这个答案显示了如何做参数化。 这是我用它的方式:

 RestAdapter.Builder builder= new RestAdapter.Builder() .setRequestInterceptor(new RequestInterceptor() { @Override public void intercept(RequestFacade request) { request.addHeader("Accept", "application/json;versions=1"); if (MyApplicationUtils.isNetworkAvailable(context)) { int maxAge = 60; // read from cache for 1 minute request.addHeader("Cache-Control", "public, max-age=" + maxAge); } else { int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale request.addHeader("Cache-Control", "public, only-if-cached, max-stale=" + maxStale); } } }); 

上面的所有链接都不适合我。 我试图在改进2.0.0-beta2中实现脱机caching。 我使用okHttpClient.networkInterceptors()方法添加了一个拦截器,但是当我试图离线使用caching时收到了java.net.UnknownHostException 。 原来,我不得不添加okHttpClient.interceptors()以及。

问题在于caching没有写入闪存,因为服务器返回了Pragma:no-cache ,这阻止了OkHttp存储响应。 即使在修改请求标头值之后,脱机caching也不起作用。 经过一些反复试验,我得到了caching工作,而不用修改后端,通过从响应中删除编译指示而不是request – response.newBuilder().removeHeader("Pragma");

改造: 2.0.0-beta2 ; OkHttp: 2.5.0

 OkHttpClient okHttpClient = createCachedClient(context); Retrofit retrofit = new Retrofit.Builder() .client(okHttpClient) .baseUrl(API_URL) .addConverterFactory(GsonConverterFactory.create()) .build(); service = retrofit.create(RestDataResource.class); 

 private OkHttpClient createCachedClient(final Context context) { File httpCacheDirectory = new File(context.getCacheDir(), "cache_file"); Cache cache = new Cache(httpCacheDirectory, 20 * 1024 * 1024); OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.setCache(cache); okHttpClient.interceptors().add( new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); String cacheHeaderValue = isOnline(context) ? "public, max-age=2419200" : "public, only-if-cached, max-stale=2419200" ; Request request = originalRequest.newBuilder().build(); Response response = chain.proceed(request); return response.newBuilder() .removeHeader("Pragma") .removeHeader("Cache-Control") .header("Cache-Control", cacheHeaderValue) .build(); } } ); okHttpClient.networkInterceptors().add( new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); String cacheHeaderValue = isOnline(context) ? "public, max-age=2419200" : "public, only-if-cached, max-stale=2419200" ; Request request = originalRequest.newBuilder().build(); Response response = chain.proceed(request); return response.newBuilder() .removeHeader("Pragma") .removeHeader("Cache-Control") .header("Cache-Control", cacheHeaderValue) .build(); } } ); return okHttpClient; } 

 public interface RestDataResource { @GET("rest-data") Call<List<RestItem>> getRestData(); } 

我的解决scheme

 private BackendService() { httpCacheDirectory = new File(context.getCacheDir(), "responses"); int cacheSize = 10 * 1024 * 1024; // 10 MiB Cache cache = new Cache(httpCacheDirectory, cacheSize); httpClient = new OkHttpClient.Builder() .addNetworkInterceptor(REWRITE_RESPONSE_INTERCEPTOR) .addInterceptor(OFFLINE_INTERCEPTOR) .cache(cache) .build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.backend.com") .client(httpClient) .addConverterFactory(GsonConverterFactory.create()) .build(); backendApi = retrofit.create(BackendApi.class); } private static final Interceptor REWRITE_RESPONSE_INTERCEPTOR = chain -> { Response originalResponse = chain.proceed(chain.request()); String cacheControl = originalResponse.header("Cache-Control"); if (cacheControl == null || cacheControl.contains("no-store") || cacheControl.contains("no-cache") || cacheControl.contains("must-revalidate") || cacheControl.contains("max-age=0")) { return originalResponse.newBuilder() .header("Cache-Control", "public, max-age=" + 10) .build(); } else { return originalResponse; } }; private static final Interceptor OFFLINE_INTERCEPTOR = chain -> { Request request = chain.request(); if (!isOnline()) { Log.d(TAG, "rewriting request"); int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale request = request.newBuilder() .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) .build(); } return chain.proceed(request); }; public static boolean isOnline() { ConnectivityManager cm = (ConnectivityManager) MyApplication.getApplication().getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo netInfo = cm.getActiveNetworkInfo(); return netInfo != null && netInfo.isConnectedOrConnecting(); } 

基于@ kosiara-bartosz-kasarzycki的回答 ,我创build了一个示例项目,使用retrofit,okhttp,rxjava和番石榴从内存 – >磁盘 – >networking正确加载。 https://github.com/digitalbuddha/StoreDemo

答案是肯定的,根据上面的答案,我开始编写unit testing来validation所有可能的用例:

  • 脱机时使用caching
  • 首先使用caching响应,直到过期,然后使用networking
  • 首先使用networking,然后caching一些请求
  • 不要在caching中存储一​​些响应

我build立了一个小型的辅助库来轻松的configurationOKHttpcaching,你可以在Github上看到相关的unit testing: https : //github.com/ncornette/OkCacheControl/blob/master/okcache-control/src/test/java/com/ ncornette /caching/ OkCacheControlTest.java

演示脱机时使用caching的Unittest:

 @Test public void test_USE_CACHE_WHEN_OFFLINE() throws Exception { //given givenResponseInCache("Expired Response in cache", -5, MINUTES); given(networkMonitor.isOnline()).willReturn(false); //when //This response is only used to not block when test fails mockWebServer.enqueue(new MockResponse().setResponseCode(404)); Response response = getResponse(); //then then(response.body().string()).isEqualTo("Expired Response in cache"); then(cache.hitCount()).isEqualTo(1); } 

正如你所看到的,即使caching已经过期,也可以使用caching。 希望它会有所帮助。

使用Retrofit2和OkHTTP3caching:

 OkHttpClient client = new OkHttpClient .Builder() .cache(new Cache(App.sApp.getCacheDir(), 10 * 1024 * 1024)) // 10 MB .addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); if (NetworkUtils.isNetworkAvailable()) { request = request.newBuilder().header("Cache-Control", "public, max-age=" + 60).build(); } else { request = request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 7).build(); } return chain.proceed(request); } }) .build(); 

NetworkUtils.isNetworkAvailable()静态方法:

 public static boolean isNetworkAvailable(Context context) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); return activeNetwork != null && activeNetwork.isConnectedOrConnecting(); } 

然后,只需将客户端添加到改造生成器:

 Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); 

原始来源: https : //newfivefour.com/android-retrofit2-okhttp3-cache-network-request-offline.html