如何缓存来自 Web 服务器的 okHTTP 响应?

Posted

技术标签:

【中文标题】如何缓存来自 Web 服务器的 okHTTP 响应?【英文标题】:How to cache okHTTP response from Web server? 【发布时间】:2018-09-02 08:39:44 【问题描述】:

我想知道如何缓存来自Web服务器的okHTTP响应(返回json数据)?

我希望我的应用程序下载 RecycleView 所需的所有数据,并在用户第一次运行应用程序时将其缓存 - 如果数据没有更改,则避免从 Web 服务器重新下载和解析所有相同的数据。

我试图获取响应标头,但这是我得到的:

Request URL: https://somedomain.com/wp-json/?categories=3&per_page=100&status=publish
Request Method: GET
Status Code: 200 OK
Remote Address: 176.28.12.139:443
Referrer Policy: no-referrer-when-downgrade
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Expose-Headers: X-WP-Total, X-WP-TotalPages
Allow: GET
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/json; charset=UTF-8
Date: Fri, 23 Mar 2018 15:41:23 GMT
Link: <https://somedomain.com/wp-json/>; rel="https://api.w.org/"
Server: nginx
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Content-Type-Options: nosniff
X-Powered-By: PleskLin
X-Powered-By: php/7.1.15
X-Robots-Tag: noindex
X-WP-Total: 43
X-WP-TotalPages: 1
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: hr,en-US;q=0.9,en;q=0.8,de;q=0.7,nb;q=0.6
Connection: keep-alive
Host: somedomain.com
Referer: https://somedomain.com/somedir/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/65.0.3325.181 Safari/537.36
categories: 3
per_page: 100
status: publish

我读到我可以从服务器响应中找到 ETAG 或 Last-Modified 以检查是否有任何更改,但正如您所见,没有这样的东西。

如果应用程序只需要在第一次运行时下载数据 - 并且之后只有在数据发生变化的情况下,您知道该怎么做吗?

【问题讨论】:

【参考方案1】:

你需要这样的缓存拦截器:

public class CacheInterceptor implements Interceptor 
    @Override
    public Response intercept(Chain chain) throws IOException 
        Response response = chain.proceed(chain.request());

        CacheControl cacheControl = new CacheControl.Builder()
                .maxAge(15, TimeUnit.MINUTES) // 15 minutes cache
                .build();

        return response.newBuilder()
                .removeHeader("Pragma")
                .removeHeader("Cache-Control")
                .header("Cache-Control", cacheControl.toString())
                .build();
    

使用Cache 将此拦截器添加到您的OkHttpClient,如下所示:

File httpCacheDirectory = new File(applicationContext.getCacheDir(), "http-cache");
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .addNetworkInterceptor(new CacheInterceptor())
            .cache(cache)
            .build();

【讨论】:

这很好用,谢谢!所以基本上这会创建 15 分钟的服务器响应缓存?当用户在半小时左右再次运行应用程序时 - 应用程序将再次缓存新的服务器响应 15 分钟?还有一个问题 - 在构建新的请求生成器时我应该使用 .cacheControl(CacheControl.FORCE_NETWORK) 吗? @unipin 是的,这会创建 15 分钟的服务响应缓存。 15 分钟后,应用程序将缓存新的响应。只有当你想强制网络请求而不使用缓存时,你才应该使用 CacheControl.FORCE_NETWORK 所以在这种情况下,如果我不使用 CacheControl.FORCE_NETWORK,应用程序仍会获取新响应 - 因为缓存会消失?再次感谢您! 离线时对我不起作用。取而代之的是 UnknownHostException。 这看起来你正在添加逻辑来拦截和添加标头到服务器从 android 端返回的内容。我强烈建议更新服务器端逻辑以返回此 max-age 标头,而不是客户端执行此操作。阅读本文档中的 #3 和 #5 以更好地解释发生了什么 (medium.com/@I_Love_Coding/…)【参考方案2】:

只需将缓存添加到您的 OkHttp 客户端。 首先,您需要创建缓存:

int cacheSize = 10 * 1024 * 1024;
Cache cache = new Cache(cacheDirectory, cacheSize);

请注意,使用此代码,您提供的缓存大小限制为 10MB。在此之后,您将使用以下代码创建您的客户端:

OkHttpClient client = new OkHttpClient.Builder()
    .cache(cache)
    .build();

客户端将尊重缓存相关的标头。您可以参考官方文档了解更多详情: https://github.com/square/okhttp/wiki/Recipes

【讨论】:

感谢您的回复!这正是我在代码中所做的——但数据的加载速度与第一次运行时相同。我想知道如果服务器没有像上面我的那样响应缓存相关的标头怎么办? 那将需要一些自定义代码。但是在尝试编写解决方案之前,让我问一下:这些是服务器始终放在您的响应中的标头,对吗?问题是:没有任何与缓存相关的标头,您必须确定缓存的超时期限。你有没有想过这个? 是的,这些标头服务器总是放在我的响应中。你能解释一下这是什么意思吗,我对此有何感想? HTTP 缓存相关标头有助于告诉您应该保留缓存值多少秒。在那几秒钟之后,您应该使缓存无效。有关完整文档,请参阅下面的链接。现在,在您的情况下,由于您没有这些信息,您应该自行决定过期时间,并将其编码到您的应用程序中 developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control 是的,我无法从服务器获取这些信息。所以这是我唯一的选择——我设置了我的缓存到期的时间——所以无论数据是否更改,应用程序都会将请求发送到服务器?我相信数据变化不大,可能一周一次,所以我想这还不错。

以上是关于如何缓存来自 Web 服务器的 okHTTP 响应?的主要内容,如果未能解决你的问题,请参考以下文章

okhttp 客户端何时缓存服务器响应?

使用OkHttp拦截器和Retrofit进行缓存

使用OkHttp拦截器和Retrofit进行缓存

OkHttp 响应缓存返回垃圾文本

OkHttp 缓存实战

okhttp:如何处理来自服务器的未请求/意外 100(继续)响应?