无法从缓存中加载数据okHttp和retrofit

14

这是我的代码,我在其中调用api,并使用retrofit定义了okhttp的缓存:

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

现在,我从我的主活动中调用这个Presenter类:

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

现在,当我有互联网连接时运行此活动时,它可以正常工作,但是当我断开互联网并再次运行此活动时,它将无法从缓存中加载数据。

如果没有互联网,我该如何从缓存中加载这些数据?


请查看 https://dev59.com/wGAg5IYBdhLWcg3wlLui - Arun Shankar
已经尝试过了,但离线模式下无法工作! - user3997016
2
服务器是否实际回复了允许/支持缓存的内容?据我所知,除非设置了正确的标头,否则OkHttp不会缓存内容。 - David Medenjak
不,服务器没有存储缓存数据!!! - user3997016
4个回答

8
你可以尝试这个:

你可以尝试这个:

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

对我来说很有效。


你确定如果我的设备离线(未连接到互联网)这仍然有效吗? - user3997016
1
在此处找到参考代码链接。演示链接在此处 - Nilesh Deokar
@NileshDeokar 参考移步至此处 - Saeed

2

您的拦截器应该检查网络连接,并相应地设置cacheHeaderValue。在这个例子中,它使用一个名为isNetworkAvailable的方法来实现:

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

2

您需要为OkHttp添加离线缓存拦截器,以便缓存响应供离线使用。您还可以将数据缓存一分钟,如果在一分钟内发送请求,则使用缓存中的数据。

/**
 * 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);
    }
}

请参照以下教程了解有关缓存数据的更多信息 - https://krtkush.github.io/2016/06/01/caching-using-okhttp-part-1.html 此外,还可以查看Stack Overflow上的这个答案 Can Retrofit with OKHttp use cache data when offline


2
可能的错误:由于您将所有内容都放在了callAllProduct()内部,因此每次都会创建新的okHttpClient和新的Cache,您没有重用旧的Cache。您的功能依赖于callAllProductcallAllProduct依赖于新的okHttpClientokHttpCient功能依赖于新缓存。将okHttpClient放在callAllProduct()体外使您依赖于每个callAllProduct调用中相同的旧okHttpClient。尝试这个方法,即使我也不知道Retrofit内部缓存如何工作。如果仍然无法正常工作,我为我的无效想法道歉,但我保证我会再次帮助您。
思路是:每次调用callAllProduct()时,您使用okHttpClient请求到Retrofit API层。 Retrofit层检查它是否已保存与您的okHttpClient相关联的数据?每个新的okHttpClient实例意味着每个新的HTTP请求,因此每个新的ID都会生成用于缓存数据。旧缓存从未使用,因为每次都使用新的okHttpClient实例。 Retrofit没有看到与您的okHttpRequest实例关联的任何ID,因此将请求转发到Internet。 Web服务器响应数据。现在,Retrofit为成功的ok HTTP客户端请求创建新的缓存ID。但是,当您每次使用新的okHttpClient时,旧的缓存ID从未被使用,因此总是发生缓存未命中。
以下代码应放在callAllProduct()体外
 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();

现在,你的callAllProduct()方法将如下所示: 这样保证每次调用callAllProduct()都使用同一个okHttpClient。

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

        }
    }

我听不懂你的意思!我的OkHttp在Presenter类里面!! - user3997016
你按照我说的试过了吗?Presenter 就像 Model 和 View 一样,只是你的逻辑分区。在 callAllProduct 方法之外实现 okHttpClient 即可。 - Uddhav P. Gautam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接