Not able to load data from cache okHttp & retrofit

2020-06-12 02:49发布

Here is my code where i am calling api and also define cache for okhttp with retrofit:

public class DemoPresenter {

    DemoView vDemoView;
    private Context mContext;

    public DemoPresenter(Context mcontext, DemoView vDemoView) {
        this.vDemoView = vDemoView;
        this.mContext = mcontext;
    }

    public void callAllProduct() {
        if (vDemoView != null) {
            vDemoView.showProductProgressBar();
        }


        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .cache(new Cache(mContext.getCacheDir(), 10 * 1024 * 1024)) // 10 MB
                .addInterceptor(new Interceptor() {
                    @Override
                    public okhttp3.Response intercept(Chain chain) throws IOException {
                        Request request = chain.request();
                        if (BasicUtility.isInternet(mContext)) {
                            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();


        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("www.xyz.com/data/")
                .addConverterFactory(GsonConverterFactory.create())
                .client(okHttpClient)
                .build();

        AllApi productApiService = retrofit.create(AllApi.class);
        Call<ProductData> call = productApiService.getProduct();

        try {
            call.enqueue(new Callback<ProductData>() {
                @Override
                public void onResponse(Call<ProductData> call, Response<ProductData> response) {
                    ArrayList<Product> alproducts = new ArrayList<>();
                    try {
                        alproducts = response.body().getProductData();
                        onSuccess(alproducts);
                    } catch (Exception e) {

                    }
                }
                @Override
                public void onFailure(Call<ProductData> call, Throwable t) {
                }
            });
        } catch (Exception e) {

        }
    }

    private void onSuccess(ArrayList<Product> alproducts) {
        if (vDemoView != null) {
            vDemoView.hideProductProgressBar();
            vDemoView.onProductSuccess(alproducts);
        }
    }
}

Now from my main activity i am calling this presenter class:

DemoPresenter mDemoPresenter = new DemoPresenter(getApplicationContext(),this);
 mDemoPresenter.callAllProduct();

Now when i run this activity with Internet connectivity then it works fine but when i disconnected internet and run this activity again then it wont load data from cache .

How can i load this data from cache if there is not internet ?

4条回答
我命由我不由天
2楼-- · 2020-06-12 03:24

You need to add an offline Cache Interceptor for OkHttp to cache the response for offline use. You can also cache data for a minute and if the request is send within a minute, the data from the cache is used.

/**
 * Interceptor to cache data and maintain it for four weeks.
 *
 * If the device is offline, stale (at most four weeks old)
 * response is fetched from the cache.
 */
private static class OfflineResponseCacheInterceptor implements Interceptor {
    @Override
    public okhttp3.Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        if (!UtilityMethods.isNetworkAvailable()) {
            request = request.newBuilder()
                    .header("Cache-Control",
                      "public, only-if-cached, max-stale=" + 2419200)
                    .build();
        }
        return chain.proceed(request);
    }
}

Follow this tutorial to understand more about caching data - https://krtkush.github.io/2016/06/01/caching-using-okhttp-part-1.html Also this Stack Overflow answer Can Retrofit with OKHttp use cache data when offline

查看更多
孤傲高冷的网名
3楼-- · 2020-06-12 03:28

Possible errors: Because you put everything inside callAllProduct(), so you each time create new okHttpClient, and new Cache, you are not reusing your old Cache. You functionally dependent on callAllProduct, callAllProduct depends on new okHttpClient, okHttpCient is functionality dependent on new cache. Putting okHttpClient outside your callAllProduct() body makes you depend on the same old okHttpClient in each callAllProduct call. Just try this even though I also don't know how Retrofit internal caching works. If it still not working, I apologise for my not working idea, but I promise, I will help you again.

The idea is: Each time you call callAllProduct(), you use okHttpClient request to Retrofit API layer. Retrofit layer checks if it already saved the data that is associated with your okHttpClient or not? Each new okHttpClient instance means each new http request, thus each new id it generates for the caching of data. Old cache is never used because each time you are using a new instance of okHttpClient. Retrofit doesn't see any associated id with your okHttpRequest instance, thus, forwards the request to internet. Web-server responses with data. Now, Retrofit creates new id for the cache for that successful ok HTTP client request. But when you use new okHttpClient each time, old cache id never used, thus there happens cache-miss always.

This below code should be outside callAllProduct() body

 int cacheSize = 10 * 1024 * 1024; /* 10 MB. Also try increasing cache size */
    public static Cache myCache = new Cache(getCacheDir(), cacheSize);

  OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .cache(myCache) // 10 MB
                .addInterceptor(new Interceptor() {
                    @Override
                    public okhttp3.Response intercept(Chain chain) throws IOException {
                        Request request = chain.request();
                        if (BasicUtility.isInternet(mContext)) {
                            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();

Now, your callAllProduct() becomes as shown below:. This guarantees you use same okHttpClient all everytime you call callAllProduct().

public void callAllProduct() {
        if (vDemoView != null) {
            vDemoView.showProductProgressBar();
        }

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("www.xyz.com/data/")
                .addConverterFactory(GsonConverterFactory.create())
                .client(okHttpClient)
                .build();

        AllApi productApiService = retrofit.create(AllApi.class);
        Call<ProductData> call = productApiService.getProduct();

        try {
            call.enqueue(new Callback<ProductData>() {
                @Override
                public void onResponse(Call<ProductData> call, Response<ProductData> response) {
                    ArrayList<Product> alproducts = new ArrayList<>();
                    try {
                        alproducts = response.body().getProductData();
                        onSuccess(alproducts);
                    } catch (Exception e) {

                    }
                }
                @Override
                public void onFailure(Call<ProductData> call, Throwable t) {
                }
            });
        } catch (Exception e) {

        }
    }
查看更多
Anthone
4楼-- · 2020-06-12 03:29

Your interceptor should check for connectivity, and set cacheHeaderValue accordingly, In this example it uses a method isNetworkAvailable to do this:

okClient.interceptors().add(
                new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Request originalRequest = chain.request();
                        String cacheHeaderValue = isNetworkAvailable(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();
                    }
                }
        );
        okClient.networkInterceptors().add(
                new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Request originalRequest = chain.request();
                        String cacheHeaderValue = isNetworkAvailable(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();
                    }
                }
        );
查看更多
男人必须洒脱
5楼-- · 2020-06-12 03:34

You can try out this :

public class DemoPresenter {

    DemoView vDemoView;
    private Context mContext;
    private static final String CACHE_CONTROL = "Cache-Control";
    private static final String TAG = DemoPresenter.class.getName();

    public DemoPresenter(Context mcontext, DemoView vDemoView) {
        this.vDemoView = vDemoView;
        this.mContext = mcontext;
    }

    public void callAllProduct() {
        if (vDemoView != null) {
            vDemoView.showProductProgressBar();
        }


        OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .addInterceptor( provideOfflineCacheInterceptor() )
            .addNetworkInterceptor( provideCacheInterceptor() )
            .cache( provideCache() )
            .build();


       /* OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .cache(new Cache(mContext.getCacheDir(), 10 * 1024 * 1024)) // 10 MB
                .addInterceptor(new Interceptor() {
                    @Override
                    public okhttp3.Response intercept(Chain chain) throws IOException {
                        Request request = chain.request();
                        if (BasicUtility.isInternet(mContext)) {
                            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();*/


        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("www.xyz.com/data/")
                .addConverterFactory(GsonConverterFactory.create())
                .client(okHttpClient)
                .build();

        AllApi productApiService = retrofit.create(AllApi.class);
        Call<ProductData> call = productApiService.getProduct();

        try {
            call.enqueue(new Callback<ProductData>() {
                @Override
                public void onResponse(Call<ProductData> call, Response<ProductData> response) {
                    ArrayList<Product> alproducts = new ArrayList<>();
                    try {
                        alproducts = response.body().getProductData();
                        onSuccess(alproducts);
                    } catch (Exception e) {

                    }
                }
                @Override
                public void onFailure(Call<ProductData> call, Throwable t) {
                }
            });
        } catch (Exception e) {

        }
    }

    private void onSuccess(ArrayList<Product> alproducts) {
        if (vDemoView != null) {
            vDemoView.hideProductProgressBar();
            vDemoView.onProductSuccess(alproducts);
        }
    }

    public Interceptor provideOfflineCacheInterceptor () {
        return new Interceptor()
        {
            @Override
            public Response intercept (Chain chain) throws IOException
            {
                Request request = chain.request();

                if (BasicUtility.isInternet(mContext))
                {
                    CacheControl cacheControl = new CacheControl.Builder()
                        .maxStale( 7, TimeUnit.DAYS )
                        .build();

                    request = request.newBuilder()
                        .cacheControl( cacheControl )
                        .build();
                }

                return chain.proceed( request );
            }
        };
    }




    public static Interceptor provideCacheInterceptor ()
    {
        return new Interceptor()
        {
            @Override
            public Response intercept (Chain chain) throws IOException
            {
                Response response = chain.proceed( chain.request() );

                // re-write response header to force use of cache
                CacheControl cacheControl = new CacheControl.Builder()
                    .maxAge( 1, TimeUnit.MINUTES )
                    .build();

                return response.newBuilder()
                    .header( CACHE_CONTROL, cacheControl.toString() )
                    .build();
            }
        };
    }

    private Cache provideCache ()
    {
        Cache cache = null;
        try
        {
            cache = new Cache( new File( mContext.getCacheDir(), "http-cache" ),
                10 * 1024 * 1024 ); // 10 MB
        }
        catch (Exception e)
        {
            Log.e(TAG, "Could not create Cache!");
        }
        return cache;
    }
}

Working well for me.

查看更多
登录 后发表回答