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);
그리고 이것은 Cache-Control 헤더를 가진 MyApi입니다
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에서 캐시의 응답을 읽도록하기 위해해야 할 다른 작업이 있습니까?
편집 :
OKHttp 2.0.x에서이 때문에 HttpResponseCache
입니다 Cache
, setResponseCache
이다setCache
답변
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 의 답변을 확인하십시오 . 응답에서 일부 헤더를 제거해야 할 수도 있습니다.
OKHttp 2.0.x (원래 답변 확인) :
OKHttp 2.0.x HttpResponseCache
가 Cache
이므로 setResponseCache
is 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);
}
}
});
답변
위의 모든 답변은 나를 위해 작동하지 않았습니다. retrofit 2.0.0-beta2 에서 오프라인 캐시를 구현하려고했습니다 . okHttpClient.networkInterceptors()
메소드를 사용하여 인터셉터를 추가 했지만 java.net.UnknownHostException
캐시를 오프라인으로 사용하려고 할 때 수신되었습니다 . 내가 추가해야한다는 것이 밝혀졌습니다 okHttpClient.interceptors()
.
문제는 서버가 리턴 Pragma:no-cache
되어 OkHttp가 응답을 저장하지 못 하기 때문에 캐시가 플래시 스토리지에 기록되지 않았다는 것 입니다. 요청 헤더 값을 수정 한 후에도 오프라인 캐시가 작동하지 않았습니다. 시행 착오 후에 요청 대신 pragma를 응답에서 제거하여 백엔드 측을 수정하지 않고 캐시가 작동하도록했습니다.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();
}
답변
위의 답변을 바탕으로 대답은 예입니다. 가능한 모든 사용 사례를 확인하기 위해 단위 테스트를 작성하기 시작했습니다.
- 오프라인 일 때 캐시 사용
- 만료 될 때까지 먼저 캐시 된 응답을 사용한 다음 네트워크
- 네트워크를 먼저 사용한 다음 일부 요청에 대해 캐시
- 일부 응답을 위해 캐시에 저장하지 마십시오
OKHttp 캐시를 쉽게 구성하기 위해 작은 도우미 라이브러리를 만들었습니다 .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);
}
보시다시피 캐시가 만료 된 경우에도 캐시를 사용할 수 있습니다. 그것이 도움이되기를 바랍니다.
답변
@ kosiara-bartosz-kasarzycki의 답변을 바탕으로, retrofit, okhttp, rxjava 및 guava를 사용하여 메모리-> 디스크-> 네트워크에서 올바르게로드하는 샘플 프로젝트를 만들었습니다.
https://github.com/digitalbuddha/StoreDemo
답변
Retrofit2 및 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 () 정적 메소드 :
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