Rétrofit avec OKHttp peut-il utiliser des données de cache lorsqu’il est hors ligne

J’essaie d’utiliser Retrofit & OKHttp pour mettre en cache les réponses HTTP. J’ai suivi cet aperçu et j’ai fini avec ce code:

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); 

Et ceci est MyApi avec les en-têtes Cache-Control

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

Je demande d’abord en ligne et vérifie les fichiers de cache. La réponse JSON correcte et les en-têtes sont là. Mais lorsque j’essaie de demander une connexion hors ligne, je reçois toujours une RetrofitError UnknownHostException . Y a-t-il autre chose que je devrais faire pour que Retrofit lise la réponse du cache?

EDIT: Puisque HttpResponseCache de OKHttp 2.0.x est Cache , setResponseCache est setCache

Edit for Retrofit 2.x:

OkHttp Interceptor est le bon moyen d’accéder au cache en mode hors connexion:

1) Créer un intercepteur:

 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) client d’installation:

 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) Ajouter le client à la modernisation

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

Vérifiez aussi la réponse de @kosiara – Bartosz Kosarzycki . Vous devrez peut-être supprimer un en-tête de la réponse.


OKHttp 2.0.x (Cochez la réponse d’origine):

Comme OKHttp 2.0.x HttpResponseCache est Cache , setResponseCache est setCache . Donc, vous devriez setCache comme ceci:

  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); } Ssortingng hostURL = context.getSsortingng(R.ssortingng.host_url); api = new RestAdapter.Builder() .setEndpoint(hostURL) .setClient(new OkClient(okHttpClient)) .setRequestInterceptor(/*rest of the answer here */) .build() .create(MyApi.class); 

Réponse originale:

Il s’avère que la réponse du serveur doit avoir Cache-Control: public pour que OkClient puisse lire le cache.

En outre, si vous souhaitez demander au réseau une fois disponible, vous devez append Cache-Control: max-age=0 tête de requête Cache-Control: max-age=0 . Cette réponse montre comment le faire paramétré. Voici comment je l’ai utilisé:

 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); } } }); 

Toutes les réponses ci-dessus n’ont pas fonctionné pour moi. J’ai essayé d’implémenter le cache hors ligne dans la version 2.0.0-beta2 . J’ai ajouté un intercepteur à l’aide de la méthode okHttpClient.networkInterceptors() mais java.net.UnknownHostException reçu une java.net.UnknownHostException lorsque j’ai essayé d’utiliser le cache hors ligne. Il s’est avéré que je devais également append okHttpClient.interceptors() .

Le problème était que le cache n’était pas écrit sur le stockage flash car le serveur Pragma:no-cache ce qui empêchait OkHttp de stocker la réponse. Le cache hors ligne ne fonctionnait pas même après la modification des valeurs d’en-tête de requête. Après quelques essais et erreurs, le cache fonctionnait sans modifier le côté backend en supprimant pragma de reponse à la place de request – response.newBuilder().removeHeader("Pragma");

Rénovation: 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(); Ssortingng 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(); Ssortingng 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> getRestData(); } 

Ma solution:

 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()); Ssortingng 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(); } 

En s’appuyant sur la réponse de @kosiara-bartosz-kasarzycki, j’ai créé un exemple de projet qui se charge correctement à partir de memory-> disk-> network en utilisant retrofit, okhttp, rxjava et guava. https://github.com/digitalbuddha/StoreDemo

La réponse est OUI, sur la base des réponses ci-dessus, j’ai commencé à écrire des tests unitaires pour vérifier tous les cas d’utilisation possibles:

  • Utiliser le cache en mode hors connexion
  • Utilisez d’abord la réponse mise en cache jusqu’à expiration, puis le réseau
  • Utilisez d’abord le réseau puis mettez en cache certaines requêtes
  • Ne pas stocker dans le cache pour certaines réponses

J’ai construit une petite bibliothèque d’aide pour configurer facilement le cache OKHttp, vous pouvez voir le lien le plus souvent ici sur Github: https://github.com/ncornette/OkCacheControl/blob/master/okcache-control/src/test/java/com/ ncornette / cache / OkCacheControlTest.java

Unittest qui démontre l’utilisation du cache en mode hors connexion:

 @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().ssortingng()).isEqualTo("Expired Response in cache"); then(cache.hitCount()).isEqualTo(1); } 

Comme vous pouvez le voir, le cache peut être utilisé même s’il a expiré. J’espère que ça va aider.

Cache avec Retrofit2 et OkHTTP3:

 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(); 

Méthode statique 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(); } 

Ensuite, ajoutez simplement le client au générateur de conversion:

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

Source originale: https://newfivefour.com/android-retrofit2-okhttp3-cache-network-request-offline.html