ওকেএইচটিটিপি দিয়ে পুনঃনির্মাণ অফলাইনে থাকা অবস্থায় ক্যাশে ডেটা ব্যবহার করতে পারে


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

প্রথমে আমি অনলাইনে অনুরোধ করছি এবং ক্যাশে ফাইলগুলি চেক করব। সঠিক জেএসওএন প্রতিক্রিয়া এবং শিরোনাম রয়েছে। তবে আমি যখন অফলাইনে অনুরোধ করার চেষ্টা করি তখন আমি সর্বদা পাই RetrofitError UnknownHostException। রেট্রোফিটকে ক্যাশে থেকে প্রতিক্রিয়াটি পড়ার জন্য আমার আরও কিছু করা উচিত?

সম্পাদনা করুন: যেহেতু OKHttp 2.0.x HttpResponseCacheহয় Cache, setResponseCacheহয়setCache


1
আপনি যে সার্ভারটি কল করছেন তা কি কোনও উপযুক্ত ক্যাশে-নিয়ন্ত্রণ শিরোনাম দিয়ে সাড়া দিচ্ছে?
হাসান ইব্রাহিম

এটি এটিকে ফিরিয়ে দেয় Cache-Control: s-maxage=1209600, max-age=1209600আমি জানি না এটি যথেষ্ট কিনা।
osrl

মনে হচ্ছে publicকীওয়ার্ডটিকে অফলাইনে কাজ করার জন্য প্রতিক্রিয়া শিরোনামে থাকা দরকার ছিল। তবে, এই শিরোলেখগুলি যখন উপলভ্য থাকে তখন OkClient নেটওয়ার্ক ব্যবহার করতে দেয় না। যখন উপলব্ধ থাকে তখন নেটওয়ার্ক ব্যবহারের জন্য ক্যাশে নীতি / কৌশল নির্ধারণের উপায় আছে কি?
ওএসআরএল

আপনি একই অনুরোধে এটি করতে পারবেন কিনা তা সম্পর্কে আমি নিশ্চিত নই। প্রাসঙ্গিক পরীক্ষা করতে পারবেন CacheControl বর্গ, এবং CacheControl হেডার। যদি এরকম কোনও আচরণ না হয়, তবে আমি সম্ভবত দুটি অনুরোধ করার সিদ্ধান্ত নেব, একটি ক্যাশেড কেবলমাত্র অনুরোধ (কেবল-যদি-ক্যাশে করা হয়), তারপরে একটি নেটওয়ার্ক (সর্বোচ্চ-বয়স = 0) এক হবে।
হাসান ইব্রাহিম

এটাই আমার মনে প্রথম জিনিস ছিল। আমি সেই ক্যাশে কন্ট্রোল এবং ক্যাশেস্ট্রজি ক্লাসে দিন কাটিয়েছি । তবে দুটি অনুরোধের ধারণাটি তেমন কোনও অর্থ দেয়নি। যদি max-stale + max-ageপাস হয়ে যায় তবে এটি নেটওয়ার্ক থেকে অনুরোধ জানায়। তবে আমি এক সপ্তাহে সর্বোচ্চ বাসি সেট করতে চাই। এটি নেটওয়ার্ক উপলব্ধ থাকলেও এটি ক্যাশে থেকে প্রতিক্রিয়া পড়তে সক্ষম করে।
osrl

উত্তর:


189

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 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 ব্যবহারের জন্য প্রকৃত ক্যাশে সেট করতে ভুলে গিয়েছি question প্রশ্নের উত্তর বা এই উত্তরে কোড দেখুন ))
জোনিক

2
শুধু উপদেশ শব্দ: HttpResponseCache has been renamed to Cache.** Install it with OkHttpClient.setCache(...) instead of OkHttpClient.setResponseCache(...)
হেনরিক ডি সোসা

2
নেটওয়ার্ক উপলভ্য না হলে আমি ইন্টারসেপ্টর কল পাই না। আমি নিশ্চিত না যে যখন নেটওয়ার্ক পাওয়া যায় না তখন কীভাবে এই পরিস্থিতি আঘাত হানে। আমি কি এখানে কিছু মিস করছি?
অ্যান্ড্রয়েডমে

2
হয় if (Utils.isNetworkAvailable(context))সঠিক অথবা এটি অর্থাত ফেরানো যায় অনুমিত হয় if (!Utils.isNetworkAvailable(context))?
এরিকন

2
আমি রেট্রোফিট ২.১.০ ব্যবহার করছি এবং ফোনটি অফলাইনে public okhttp3.Response intercept(Chain chain) throws IOExceptionথাকা অবস্থায় কখনই কল করা হয় না, আমি অনলাইনে থাকাকালীন তখনই ডাকা হয়
এরিক

28

উপরের সমস্ত আনসার আমার পক্ষে কাজ করেনি। আমি অফলাইন ক্যাশে বাস্তবায়ন করার চেষ্টা রেট্রোফিট 2.0.0-beta2 । আমি okHttpClient.networkInterceptors()পদ্ধতিটি ব্যবহার করে একটি ইন্টারসেপ্টর যুক্ত java.net.UnknownHostExceptionকরেছি তবে ক্যাশে অফলাইনে ব্যবহার করার চেষ্টা করার সময় পেয়েছি । দেখা গেল যে আমাকেও যুক্ত করতে হয়েছিল okHttpClient.interceptors()

সমস্যাটি হ'ল ক্যাশে ফ্ল্যাশ স্টোরেজে লেখা হয়নি কারণ সার্ভার ফিরে এসেছে Pragma:no-cacheযা OkHttp কে প্রতিক্রিয়া সঞ্চয় করতে বাধা দেয়। অনুরোধ শিরোনামের মানগুলি সংশোধন করার পরেও অফলাইন ক্যাশে কাজ করে না। কিছু ট্রায়াল-এন্ড-ত্রুটির পরে আমি অনুরোধের পরিবর্তে প্রগমাটি পুনরায় থেকে সরিয়ে ব্যাকএন্ড পাশটি সংশোধন না করে কাজ করতে ক্যাশে পেয়েছি -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();

}

6
এটি দেখতে আপনার মতো interceptors ()এবং networkInterceptors ()অভিন্ন। কেন আপনি এটির সদৃশ করলেন?
toobsco42

বিভিন্ন ধরণের ইন্টারসেপ্টর এখানে পড়েন। github.com/square/okhttp/wiki/Interceptors
রোহিত বান্দিল

হ্যাঁ তবে তারা উভয়ই একই কাজ করে তাই আমি বেশ নিশ্চিত যে ১ টি ইন্টারসেপ্টারের যথেষ্ট হওয়া উচিত, তাই না?
ওভিদিউ লাটুক

সেখানে একটি নির্দিষ্ট কারণ নেই না উভয়ের জন্য একই আটককারী উদাহরণস্বরূপ ব্যবহার .networkInterceptors().add()এবং interceptors().add()?
সিসিপিজ্জা

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 অসন্তুষ্টির অনুরোধ রইল (কেবল-যদি ক্যাশে থাকে)
জাকারিয়া

কেবলমাত্র আপনার সমাধান আমাকে সহায়তা করে, অনেক ধন্যবাদ। স্ক্রোল ডাউন করতে 2 দিন
অপচয় করুন

1
হ্যাঁ, আমার ক্ষেত্রে একমাত্র কার্যকরী সমাধান। (Retrofit 1.9.x + OkHttp3)
হোয়াং

1
রেট্রোফিট RETROFIT_VERSION সাথে কাজ করে = 2.2.0 OKHTTP_VERSION = 3.6.0
Tadas Valaitis

অনুমোদনের সাথে এপিআই অ্যাক্সেস করতে কীভাবে এই পদ্ধতিতে বিল্ডার.এডএডহেডার () যুক্ত করবেন?
অভিলাষ

6

উত্তরটি হ্যাঁ, উপরের উত্তরের উপর ভিত্তি করে, সমস্ত সম্ভাব্য ব্যবহারের ক্ষেত্রে যাচাই করার জন্য আমি ইউনিট পরীক্ষা লিখতে শুরু করেছি:

  • অফলাইনে থাকাকালীন ক্যাশে ব্যবহার করুন
  • মেয়াদ উত্তীর্ণ হওয়া পর্যন্ত প্রথমে ক্যাশেড প্রতিক্রিয়া ব্যবহার করুন, তারপরে নেটওয়ার্ক
  • প্রথমে নেটওয়ার্ক ব্যবহার করুন তারপরে কিছু অনুরোধের জন্য ক্যাশে
  • কিছু প্রতিক্রিয়ার জন্য ক্যাশে সংরক্ষণ করবেন না

আমি ওকে এইচটিএইচপি ক্যাশে সহজেই কনফিগার করতে একটি ছোট সহায়ক সহায়ক তৈরি করেছি, আপনি এখানে সম্পর্কিত ইউনিটটি দেখতে পাবেন গিথুব: https://github.com/ncornette/OkCacheControl/blob/master/okcache-control/src/test/java/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
আপনার lib দুর্দান্ত! আপনার কঠোর পরিশ্রমের জন্য ধন্যবাদ। এই লিবা
হোয়াং

5

@ কোসিয়ারা-বার্টোস-কসরজিখির উত্তরে বিল্ডিং করে , আমি একটি নমুনা প্রকল্প তৈরি করেছি যা রেট্রোফিট, ওখট্টপ, আরএক্সজাভা এবং পেয়ারা ব্যবহার করে মেমরি-> ডিস্ক-> নেটওয়ার্ক থেকে সঠিকভাবে লোড হয়। 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();

নেটওয়ার্ক ইউটিস.আইস নেটওয়ার্কভ্যালি উপলদ্ধি () স্থির পদ্ধতি:

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
আমি যখন প্রথম অফলাইন মোড দিয়ে লোড করি তখন তা ক্রাশ হয়! অন্যথায় এটি সঠিকভাবে কাজ করছে
জাফার সেলালাগলু

এটি আমার পক্ষে কাজ করে না। আমি এর নীতিটি সংহত করার চেষ্টা করার পরে আমি এটি অনুলিপি করে দিয়েছি, তবে এটি কাজ করার জন্য নেটটি করে do
ছেলে

App.sApp.getCacheDir () এটি কী করে?
হুযাইফা আসিফ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.