जब ऑफ़लाइन हो तो OKHttp के साथ रेट्रोफ़िट कैश डेटा का उपयोग कर सकता है


148

मैं HTTP प्रतिक्रियाओं का कैश करने के लिए Retrofit और OKHttp का उपयोग करने की कोशिश कर रहा हूं। मैंने इस जिस्ट का अनुसरण किया और इस कोड के साथ समाप्त हुआ:

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 है

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। क्या मुझे कैश से प्रतिक्रिया पढ़ने के लिए कुछ और करना चाहिए?

संपादित करें: चूंकि OKHttp 2.0.x HttpResponseCacheहै Cache, setResponseCacheहैsetCache


1
क्या वह सर्वर है जिसे आप उचित कैश-कंट्रोल हेडर के साथ जवाब दे रहे हैं?
हसन इब्राहीम

यह इस रिटर्न Cache-Control: s-maxage=1209600, max-age=1209600अगर यह काफी है मैं नहीं जानता।
ऑसरेल

ऐसा लगता है कि publicकीवर्ड को ऑफ़लाइन करने के लिए प्रतिक्रिया हेडर में होना चाहिए था। लेकिन, ये हेडर उपलब्ध होने पर OkClient को नेटवर्क का उपयोग करने की अनुमति नहीं देता है। उपलब्ध होने पर नेटवर्क का उपयोग करने के लिए कैश पॉलिसी / रणनीति निर्धारित करने के लिए वैसे भी क्या है?
ऑक्सले

मुझे यकीन नहीं है कि आप एक ही अनुरोध में ऐसा कर सकते हैं। आप संबंधित CacheControl वर्ग और Cache- नियंत्रण शीर्ष लेख देख सकते हैं। यदि ऐसा कोई व्यवहार नहीं है, तो मैं शायद दो अनुरोध करने का विकल्प चुनूंगा, एक कैशेड केवल अनुरोध (केवल-अगर-कैश किया गया), उसके बाद एक नेटवर्क (अधिकतम-आयु = 0) एक।
हसन इब्राहीम

मेरे दिमाग में सबसे पहले यही बात आई थी। मैंने उस CacheControl और CacheStrategy क्लासेस में दिन बिताए । लेकिन दो अनुरोधों का विचार बहुत मायने नहीं रखता था। यदि max-stale + max-ageपारित हो जाता है, तो यह नेटवर्क से अनुरोध करता है। लेकिन मैं एक सप्ताह में अधिकतम-बासी सेट करना चाहता हूं। यह नेटवर्क उपलब्ध है, भले ही यह कैश से प्रतिक्रिया पढ़ें।
ऑक्सले

जवाबों:


189

2. रेट्रोफ़िट के लिए संपादित करें 2.x:

ऑफ़लाइन होने पर कैश एक्सेस करने का सही तरीका है OkHttp Interceptor:

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है 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);
            }
        }
});

(मैं सोच रहा था कि क्यों यह काम नहीं किया था, पता चला मैं उपयोग करने के लिए OkHttpClient के लिए वास्तविक कैश सेट करने के लिए भूल गया प्रश्न में या में कोड देखें। इस जवाब ।)
Jonik

2
बस सलाह का एक शब्द HttpResponseCache has been renamed to Cache.** Install it with OkHttpClient.setCache(...) instead of OkHttpClient.setResponseCache(...):।
हेनरिक डी सूसा

2
जब नेटवर्क उपलब्ध नहीं होता है तो मुझे इंटरसेप्टर नहीं कहा जाता है। मुझे यकीन नहीं है कि जब नेटवर्क उपलब्ध नहीं होगा तो स्थिति कैसी होगी। क्या मुझसे कोई चूक हो रही है?
Androidme

2
है if (Utils.isNetworkAvailable(context))सही है या यह यानी स्थिति में कर दिया जाता है if (!Utils.isNetworkAvailable(context))?
एरन

2
मैं रिट्रोफिट 2.1.0 का उपयोग कर रहा हूं और जब फोन ऑफ़लाइन होता है, तो public okhttp3.Response intercept(Chain chain) throws IOExceptionउसे कभी भी कॉल नहीं किया जाता है, इसे केवल तभी कहा जाता है जब मैं ऑनलाइन हूं
बजे

28

ऊपर के सभी खिलाड़ी मेरे लिए काम नहीं करते थे। मैंने रेट्रोफ़िट 2.0.0-बीटा 2 में ऑफ़लाइन कैश लागू करने का प्रयास किया । मैंने okHttpClient.networkInterceptors()विधि का उपयोग करके एक इंटरसेप्टर जोड़ा लेकिन java.net.UnknownHostExceptionजब मैंने कैश को ऑफ़लाइन उपयोग करने का प्रयास किया। यह पता चला कि मुझे भी जोड़ना था okHttpClient.interceptors()

समस्या यह थी कि कैश को फ्लैश स्टोरेज के लिए नहीं लिखा गया था क्योंकि सर्वर वापस आ गया था Pragma:no-cacheजो ओकेहटप को प्रतिक्रिया को संग्रहीत करने से रोकता है। अनुरोध शीर्ष लेख मानों को संशोधित करने के बाद भी ऑफ़लाइन कैश काम नहीं किया। कुछ ट्रायल-एंड-एरर के बाद, मुझे अनुरोध के बजाय रिपॉजिट से प्रैग्मैड को हटाकर बैकेंड की ओर संशोधित किए बिना काम करने के लिए कैश मिला -response.newBuilder().removeHeader("Pragma");

रेट्रोफिट: 2.0.0-बीटा 2 ; 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();

}

6
यह आपकी तरह दिखता है interceptors ()और networkInterceptors ()समान हैं। आपने यह नकल क्यों की?
तोबस्को 42

विभिन्न प्रकार के इंटरसेप्टर यहां पढ़ते हैं। github.com/square/okhttp/wiki/Interceptors
रोहित Bandil

हाँ, लेकिन वे दोनों एक ही काम करते हैं इसलिए मुझे पूरा यकीन है कि 1 इंटरसेप्टर पर्याप्त होना चाहिए, है ना?
Ovidiu Latcu

वहाँ दोनों के लिए एक ही इंटरसेप्टर उदाहरण का उपयोग नहीं करने के लिए एक विशिष्ट कारण है .networkInterceptors().add()और interceptors().add()?
ccpizza

22

मेरा समाधान:

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

3
मेरे लिए काम नहीं कर रहा है ... 504 असंतोषजनक अनुरोध (केवल-अगर कैश किया गया हो रहा है)
zacharia

केवल तुम्हारा समाधान मेरी मदद करता है, बहुत बहुत धन्यवाद। नीचे स्क्रॉल करने के लिए 2 दिन का समय
срослав Мовчан

1
हाँ, मेरे मामले में एकमात्र काम कर समाधान। (रेट्रोफिट 1.9.x + okHttp3)
होआंग गुयेन

1
रेट्रोफिट RETROFIT_VERSION = 2.2.0 OKHTTP_VERSION = 3.6.0 के साथ काम करता है
तदास वेलाइटिस

कैसे प्राधिकरण के साथ एपीआई का उपयोग करने के लिए इस विधि में build.addheader () को जोड़ने के लिए?
अभिलाष

6

उत्तर हां है, उपरोक्त उत्तरों के आधार पर, मैंने सभी संभावित उपयोग मामलों को सत्यापित करने के लिए यूनिट परीक्षण लिखना शुरू किया:

  • ऑफ़लाइन होने पर कैश का उपयोग करें
  • पहले समाप्त हो चुकी है, तब नेटवर्क तक कैश्ड प्रतिक्रिया का उपयोग करें
  • कुछ अनुरोधों के लिए पहले कैश का उपयोग करें
  • कुछ प्रतिक्रियाओं के लिए कैश में स्टोर न करें

मैंने OKHttp कैश को आसानी से कॉन्फ़िगर करने के लिए एक छोटा सहायक कामगार बनाया, आप संबंधित गिट्टब पर यहां देख सकते हैं: https://github.com/ncornette/OkCacheControl/blob/master/okcache-control/src/test/java/com/com ncornette / कैश / OkCacheControlTest.java

ऑफ़लाइन होने पर कैश का उपयोग दर्शाता है

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

जैसा कि आप देख सकते हैं, कैश का उपयोग किया जा सकता है भले ही वह समाप्त हो गया हो। आशा है कि यह मदद करेगा।


2
आपका दायित्व महान है! आपकी कड़ी मेहनत के लिए धन्यवाद। द लिब: github.com/ncornette/OkCacheControl
Hoang Nguyen Huu

5

@ kosiara-bartosz-kasarzycki के उत्तर पर निर्माण करते हुए , मैंने एक नमूना प्रोजेक्ट बनाया, जो ठीक से मेमोरी-> डिस्क-> नेटवर्क से रेट्रोफिट, ओक्हेटप, आरएक्सजवा और अमरूद का उपयोग कर लोड करता है। https://github.com/digitalbuddha/StoreDemo


2

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


1
जब मैं पहली बार ऑफ़लाइन मोड से लोड करता हूं, तो यह दुर्घटनाग्रस्त हो जाता है! अन्यथा यह ठीक से काम कर रहा है
ज़ैफ़र सेलोग्लू

यह मेरे लिए काम नहीं करता है। जब मैंने इसके सिद्धांत को एकीकृत करने की कोशिश की, तब मैंने इसे कॉपी-पेस्ट किया, लेकिन नेट ने इसे काम में ला दिया।
लड़का

App.sApp.getCacheDir () यह क्या करता है?
हुजैफा आसिफ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.