Możliwość doposażenia OKHttp w dane pamięci podręcznej w trybie offline
Próbuję użyć Retrofit & OKHttp do buforowania odpowiedzi HTTP. Podążałem za tym gist i, skończyło się na tym kodzie:
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);
A to MyApi z nagłówkami 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<Person> callback
);
Najpierw żądam online i sprawdzam pliki pamięci podręcznej. Prawidłowa odpowiedź JSON i nagłówki są tam. Ale kiedy próbuję poprosić offline, zawsze dostaję RetrofitError UnknownHostException
. Czy jest coś jeszcze, co powinienem zrobić, aby Retrofit odczytał odpowiedź z pamięci podręcznej?
EDIT:
Od OKHttp 2.0.x HttpResponseCache
jest Cache
, setResponseCache
is setCache
6 answers
Edycja dla Retrofit 2.x:
OkHttp Interceptor jest właściwym sposobem dostępu do pamięci podręcznej w trybie offline:]}1) Utwórz Interceptor:
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) klient konfiguracji:
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) Dodaj klienta do retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
Sprawdź też @kosiara-Bartosz Kosarzycki'S odpowiedz. Może być konieczne usunięcie nagłówka z odpowiedzi.
OKHttp 2.0.x (Sprawdź oryginalną odpowiedź):
Od OKHttp 2.0.x HttpResponseCache
jest Cache
, setResponseCache
is setCache
. Więc powinieneś setCache
tak:
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);
Oryginalna Odpowiedź:
Okazuje się, że odpowiedź serwera musi mieć Cache-Control: public
, aby OkClient
odczytywać z pamięci podręcznej.
Również jeśli chcesz zażądać od sieci, jeśli jest to możliwe, powinieneś dodać Cache-Control: max-age=0
nagłówek żądania. ta odpowiedź pokazuje, jak to zrobić sparametryzowane. Tak go używałem:
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);
}
}
});
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:37
Wszystkie powyższe anwsery nie działały dla mnie. Próbowałem zaimplementować pamięć podręczną offline w retrofit 2.0.0-beta2. Dodałem interceptor za pomocą metody okHttpClient.networkInterceptors()
, ale otrzymałem java.net.UnknownHostException
, gdy próbowałem korzystać z pamięci podręcznej w trybie offline. Okazało się, że musiałem dodać okHttpClient.interceptors()
.
Problem polegał na tym, że pamięć podręczna nie została zapisana do pamięci flash, ponieważ serwer zwrócił Pragma:no-cache
, co uniemożliwia OkHttp przechowywanie odpowiedzi. Pamięć podręczna Offline nie działała nawet po modyfikacji wartości nagłówka żądania. Po kilku próbach i błędach udało mi się uruchomić cache bez modyfikowania strony zaplecza, usuwając pragmę z reponse zamiast request - response.newBuilder().removeHeader("Pragma");
Retrofit: 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();
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-16 16:34:06
Moje rozwiązanie:
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();
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-14 14:41:10
Bazując na odpowiedzi @kosiara-bartosz-kasarzycki , stworzyłem przykładowy projekt, który poprawnie ładuje się z pamięci - > dysku - > sieci za pomocą retrofit, okhttp, rxjava i guava. https://github.com/digitalbuddha/StoreDemo
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:47
Odpowiedź brzmi tak, na podstawie powyższych odpowiedzi zacząłem pisać testy jednostkowe, aby zweryfikować wszystkie możliwe przypadki użycia:
- Użyj pamięci podręcznej w trybie offline
- Użyj buforowanej odpowiedzi najpierw do wygaśnięcia, a następnie do sieci
- Użyj najpierw sieci, a następnie pamięci podręcznej dla niektórych żądań
- nie przechowuj w pamięci podręcznej niektórych odpowiedzi
Zbudowałem mały helper lib, aby łatwo skonfigurować pamięć podręczną OKHttp, możesz zobaczyć powiązany unittest tutaj na Githubie : https://github.com/ncornette/OkCacheControl/blob/master/okcache-control/src/test/java/com/ncornette/cache/OkCacheControlTest.java
Unittest pokazujący użycie pamięci podręcznej w trybie offline :
@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);
}
Jak widać, pamięć podręczna może być używana nawet jeśli wygasła. Mam nadzieję, że to pomoże.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-24 07:55:44
Cache z Retrofit2 i 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();
NetworkUtils.isNetworkAvailable () metoda statyczna:
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null &&
activeNetwork.isConnectedOrConnecting();
}
Następnie po prostu dodaj klienta do konstruktora modernizacyjnego:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
Oryginalne źródło: https://newfivefour.com/android-retrofit2-okhttp3-cache-network-request-offline.html
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-29 07:17:22