caching - memoria - que pasa si borro los datos de whatsapp
Puede actualizar con OKHttp usar datos de caché cuando no está conectado (6)
Intento usar Retrofit y OKHttp para almacenar en caché las respuestas HTTP. Seguí esta esencia y terminé con este código:
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);
Y este es MyApi con los encabezados de 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
);
Primero lo solicito en línea y verifico los archivos de caché. La respuesta JSON correcta y los encabezados están ahí. Pero cuando intento solicitar sin conexión, siempre obtengo RetrofitError UnknownHostException
. ¿Hay algo más que deba hacer para que Retrofit lea la respuesta del caché?
EDITAR: desde OKHttp 2.0.x HttpResponseCache
es Cache
, setResponseCache
está setCache
Editar para Retrofit 2.x:
OkHttp Interceptor es la forma correcta de acceder al caché cuando está fuera de línea:
1) Crear 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) Configurar el cliente:
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) Agregar cliente para la actualización
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
También verifique la respuesta de @kosiara - Bartosz Kosarzycki . Es posible que deba eliminar algún encabezado de la respuesta.
OKHttp 2.0.x (Verifique la respuesta original):
Como OKHttp 2.0.x HttpResponseCache
es Cache
, setResponseCache
es setCache
. Entonces deberías setCache
esta manera:
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);
Respuesta Original:
Resulta que la respuesta del servidor debe tener Cache-Control: public
para hacer que OkClient
lea de la caché.
Además, si desea solicitar desde la red cuando esté disponible, debe agregar Cache-Control: max-age=0
encabezado de solicitud. Esta respuesta muestra cómo hacerlo parametrizado. Así es como lo usé:
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);
}
}
});
Basándome en la answer @kosiara-bartosz-kasarzycki, creé un proyecto de muestra que se carga correctamente desde la memoria-> disco-> red usando retrofit, okhttp, rxjava y guava. https://github.com/digitalbuddha/StoreDemo
Caché con Retrofit2 y 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étodo estático de 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();
}
A continuación, solo agregue el cliente al generador de modificaciones:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
Fuente original: https://newfivefour.com/android-retrofit2-okhttp3-cache-network-request-offline.html
La respuesta es SÍ, basada en las respuestas anteriores, comencé a escribir pruebas unitarias para verificar todos los casos de uso posibles:
- Usar caché cuando no estés conectado
- Utilice la respuesta en caché primero hasta que caduque, luego red
- Primero use la red y la memoria caché para algunas solicitudes
- No almacenar en caché para algunas respuestas
Creé una pequeña ayuda lib para configurar fácilmente el caché OKHttp, puede ver el examen de unidad relacionado aquí en Github: https://github.com/ncornette/OkCacheControl/blob/master/okcache-control/src/test/java/com/ncornette/cache/OkCacheControlTest.java
Unittest que demuestra el uso de caché cuando no está conectado:
@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);
}
Como puede ver, la memoria caché se puede usar incluso si ha expirado. Espero que ayude
Mi solución:
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();
}
Todos los indicadores anteriores no funcionaron para mí. Intenté implementar el caché sin conexión en la actualización 2.0.0-beta2 . okHttpClient.networkInterceptors()
un interceptor usando el método okHttpClient.networkInterceptors()
pero recibí java.net.UnknownHostException
cuando traté de usar la memoria caché fuera de línea. Resultó que también tuve que agregar okHttpClient.interceptors()
.
El problema era que el caché no se había escrito en el almacenamiento flash porque el servidor devolvía Pragma:no-cache
que evita que OkHttp almacene la respuesta. La memoria caché sin conexión no funcionó incluso después de modificar los valores del encabezado de la solicitud. Después de un poco de prueba y error, conseguí que la memoria caché funcionara sin modificar el lado del back-end al eliminar pragma de la respuesta en lugar de la solicitud - 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();
}