Может ли модернизация с помощью OKHttp использовать данные кэша в автономном режиме
Я пытаюсь использовать Retrofit & OKHttp для кэширования HTTP-ответов. Я последовал в этом суть и, в конечном итоге с помощью этого кода:
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);
а это MyApi с заголовками 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
);
сначала я запрашиваю онлайн и проверяю файлы кэша. Правильный ответ JSON и заголовки есть. Но когда я пытаюсь запросить в автономном режиме, я всегда получаю RetrofitError UnknownHostException
. Есть ли что-нибудь еще, что я должен сделать, чтобы сделать Retrofit прочитать ответ от кэш?
EDIT:
С OKHttp 2.0.x HttpResponseCache
- это Cache
,setResponseCache
и setCache
6 ответов:
изменить для Retrofit 2.x:
перехватчик OkHttp-это правильный способ доступа к кэшу в автономном режиме:
1) Создать Перехватчик:
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 ' s ответ. Возможно, вам придется удалить какой-то заголовок из ответа.
OKHttp 2.0.x (Проверьте исходный ответ):
С OKHttp 2.0.x
HttpResponseCache
иCache
,setResponseCache
иsetCache
. Так что вы должны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);
Оригинальный Ответ:
получается, что ответ сервера должен быть
Cache-Control: public
сделатьOkClient
читать из кэша.кроме того, если вы хотите запросить из сети, когда это возможно, вы должны добавить
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); } } });
все anwsers выше не работает для меня. Я попытался реализовать автономный кэш в дооснащение 2.0.0-beta2. Я добавил перехватчик с помощью
okHttpClient.networkInterceptors()
метод, но получилjava.net.UnknownHostException
когда я пытался использовать кэш в автономном режиме. Оказалось, что мне пришлось добавитьokHttpClient.interceptors()
как хорошо.проблема была в том, что кэш не был записан на флэш-память, потому что сервер вернулся
Pragma:no-cache
что предотвращает OkHttp от хранения ответа. Автономный кэш не работал даже после изменения значения заголовка запроса. После некоторых проб и ошибок я получил кэш для работы без изменения серверной части, удалив pragma из reponse вместо запроса -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(); }
мое решение:
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-Бартош-kasarzycki это ответ, Я создал пример проекта, который правильно загружается из памяти - > диск - > сеть с помощью retrofit, okhttp, rxjava и guava. https://github.com/digitalbuddha/StoreDemo
ответ да, основываясь на приведенных выше ответах, я начал писать модульные тесты для проверки всех возможных вариантов использования:
- использовать кэш в автономном режиме
- сначала используйте кэшированный ответ до истечения срока действия, а затем сеть
- сначала используйте сеть, а затем кэш для некоторых запросов
- не хранить в кэше для некоторых ответах
Я построил небольшой вспомогательный lib для настройки кэша OKHttp легко, вы можете увидеть соответствующий unittest здесь, на Github : https://github.com/ncornette/OkCacheControl/blob/master/okcache-control/src/test/java/com/ncornette/cache/OkCacheControlTest.java
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); }
Как вы можете видеть, кэш может быть использован, даже если он истек. Надеюсь, это поможет.
кэш с Retrofit2 и Охттп3:
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