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

Posted

技术标签:

【中文标题】无法从缓存 okHttp 和改造中加载数据【英文标题】:Not able to load data from cache okHttp & retrofit 【发布时间】:2017-12-29 22:22:35 【问题描述】:

这是我的代码,我在其中调用 api 并通过改造为 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);
        
    

现在从我的主要活动中,我正在调用这个演示者类:

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

现在,当我通过 Internet 连接运行此活动时,它可以正常工作,但是当我断开 Internet 并再次运行此活动时,它不会从缓存中加载数据。

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

【问题讨论】:

看看***.com/questions/23429046/… 已经试过了,离线模式下不行! 服务器是否真的回复了允许/支持缓存的内容?除非设置了正确的标头,否则 Afaik OkHttp 不会缓存内容。 没有。服务器没有存储缓存数据!!! 【参考方案1】:

你可以试试这个:

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;
    

对我来说很好。

【讨论】:

如果我的设备离线(未连接到互联网),你确定这能正常工作吗? 找到参考代码here和演示链接here @NileshDeokar 参考移动到here。【参考方案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();
                    
                
        );

【讨论】:

【参考方案3】:

你需要为 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 还有这个堆栈溢出答案 Can Retrofit with OKHttp use cache data when offline

【讨论】:

【参考方案4】:

可能的错误:因为你把所有的东西都放在callAllProduct()里面,所以你每次都创建新的okHttpClient和新的Cache,你没有重复使用旧的Cache。您在功能上依赖于 callAllProduct,callAllProduct 依赖于新的 okHttpClient,okHttpCient 是依赖于新缓存的功能。将 okHttpClient 放在 callAllProduct() 主体之外会使您在每个 callAllProduct 调用中依赖相同的旧 okHttpClient。即使我也不知道 Retrofit 内部缓存是如何工作的,也可以试试这个。如果它仍然不起作用,我为我的不工作的想法道歉,但我保证,我会再次帮助你。

思路是: 每次调用 callAllProduct() 时,都会使用 okHttpClient 请求来改造 API 层。改造层检查它是否已经保存了与您的 okHttpClient 关联的数据?每个新的 okHttpClient 实例意味着每个新的 http 请求,因此它为数据缓存生成的每个新 id。永远不会使用旧缓存,因为每次您使用 okHttpClient 的新实例。 Retrofit 没有看到任何与您的 okHttpRequest 实例关联的 id,因此,将请求转发到 Internet。带有数据的 Web 服务器响应。现在,Retrofit 为成功的 ok HTTP 客户端请求创建新的缓存 ID。但是当你每次使用新的 okHttpClient 时,旧的缓存 id 从未使用过,因此总是会发生缓存未命中。

下面的代码应该在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();

现在,您的 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) 

        
    

【讨论】:

我没找到你!我的 okhtpp 在演示者类中!! 你按照我说的尝试了吗? Presenter 只是你的逻辑分区,就像 Model 和 View 一样。只需在 callAllProduct 主体之外实现 okHttpClient。

以上是关于无法从缓存 okHttp 和改造中加载数据的主要内容,如果未能解决你的问题,请参考以下文章

Android WebView 未从缓存中加载第二页

Retrofit 中的 Enqueue 方法不会在 Fragment 中加载数据

图像在 iframe 中加载后未从缓存中加载

从缓存中加载 webview

我可以从缓存中加载后退按钮吗?

node中的优先从缓存中加载模块与模块的加载规则