wake-up-neo.com

Kann mit OKHttp nachgerüstet werden, wenn die Cache-Daten offline sind

Ich versuche, mit Retrofit & OKHttp HTTP-Antworten zwischenzuspeichern. Ich folgte this Gist und endete mit diesem 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);

Und das ist MyApi mit den Cache-Control-Headern

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

Zuerst fordere ich online an und überprüfe die Cache-Dateien. Die richtige JSON-Antwort und die richtigen Header sind vorhanden. Aber wenn ich versuche, offline anzufordern, erhalte ich immer RetrofitError UnknownHostException. Gibt es noch etwas, was ich tun sollte, damit Retrofit die Antwort aus dem Cache liest?

EDIT: Da OKHttp 2.0.x HttpResponseCacheCache ist, ist setResponseCachesetCache

140
osrl

Edit für Retrofit 2.x:

OkHttp Interceptor ist der richtige Weg, um offline auf den Cache zuzugreifen:

1) Interceptor erstellen:

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) Setup-Client:

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) Client zum Nachrüsten hinzufügen

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

Überprüfen Sie auch @ kosiara - Bartosz Kosarzycki 's Antwort . Möglicherweise müssen Sie einige Header aus der Antwort entfernen.


OKHttp 2.0.x (Überprüfen Sie die ursprüngliche Antwort):

Da OKHttp 2.0.x HttpResponseCacheCache ist, ist setResponseCachesetCache. Also solltest du setCache so sehen:

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

Ursprüngliche Antwort:

Es stellt sich heraus, dass die Serverantwort Cache-Control: public Haben muss, damit OkClient aus dem Cache gelesen werden kann.

Auch wenn Sie vom Netzwerk anfordern möchten, sollten Sie den Anforderungsheader Cache-Control: max-age=0 Hinzufügen. Diese Antwort zeigt, wie es parametriert wird. So habe ich es benutzt:

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

Alle Antworten oben haben bei mir nicht funktioniert. Ich habe versucht, den Offline-Cache in retrofit 2.0.0-beta2 zu implementieren. Ich habe einen Interceptor mit der okHttpClient.networkInterceptors() -Methode hinzugefügt, aber Java.net.UnknownHostException Erhalten, als ich versuchte, den Cache offline zu verwenden. Es stellte sich heraus, dass ich auch okHttpClient.interceptors() hinzufügen musste.

Das Problem war, dass der Cache nicht in den Flash-Speicher geschrieben wurde, da der Server Pragma:no-cache Zurückgab, wodurch OkHttp die Antwort nicht speichern konnte. Der Offline-Cache funktionierte auch nach dem Ändern der Anforderungsheaderwerte nicht. Nach einigem Ausprobieren habe ich den Cache zum Laufen gebracht, ohne die Backend-Seite zu verändern, indem ich Pragma aus der Antwort entfernte, anstatt die Anfrage - response.newBuilder().removeHeader("Pragma");

Nachrüstung: 2.0.0-beta2; OkHttp: 2.5.

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

}

Meine Lösung:

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

Die Antwort lautet JA, basierend auf den obigen Antworten habe ich begonnen, Komponententests zu schreiben, um alle möglichen Anwendungsfälle zu überprüfen:

  • Cache offline verwenden
  • Verwenden Sie zuerst die zwischengespeicherte Antwort, bis sie abgelaufen ist, und dann das Netzwerk
  • Verwenden Sie zuerst das Netzwerk und dann den Cache für einige Anforderungen
  • Für einige Antworten nicht im Cache speichern

Ich habe eine kleine Hilfsbibliothek erstellt, um den OKHttp-Cache einfach zu konfigurieren. Die dazugehörige Hilfsbibliothek finden Sie hier auf Github: https://github.com/ncornette/OkCacheControl/blob/master/okcache-control/src/test/ Java/com/ncornette/cache/OkCacheControlTest.Java

Unittest, das die Verwendung des Cache im Offlinemodus demonstriert:

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

Wie Sie sehen, kann der Cache auch dann verwendet werden, wenn er abgelaufen ist. Hoffe es wird helfen.

5

aufbauend auf @ kosiara-bartosz-kasarzyckis Antwort habe ich ein Beispielprojekt erstellt, das mithilfe von Retrofit, OKHTP, RXJAVA und GUAVA ordnungsgemäß von Memory-> Disk-> Network geladen wird. https://github.com/digitalbuddha/StoreDemo

5
FriendlyMikhail

Cache mit Retrofit2 und 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();

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

Dann fügen Sie einfach den Client zum Retrofit Builder hinzu:

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

Ursprüngliche Quelle: https://newfivefour.com/Android-retrofit2-okhttp3-cache-network-request-offline.html