【问题标题】:HttpResponseCache doesn`t clear old files?HttpResponseCache 不清除旧文件?
【发布时间】:2014-07-07 15:16:41
【问题描述】:

我将很快从事一个项目,该项目主要使用 HTTPRequestsJSONs 和 Images,所以我认为考虑缓存是个好主意。基本上我正在寻找解决方案

  1. 使用给定的生命周期(例如 3、6、12 小时)启动 HTTPRequest
  2. 检查该请求是否在缓存中可用并且仍然有效(生命周期)
  3. 如果请求仍然有效,则从缓存中取出,否则发出请求并保存其响应

我在 Android 中找到了 HttpResponseCache 类。它正在工作,但是它没有像我预期的那样工作。

我的测试用例是AsyncTask,用于缓存多个图像。代码如下所示:

URL url = new URL(link);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

Bitmap myBitmap;
try {
    connection.addRequestProperty("Cache-Control","only-if-cached");

    //check if Request is in cache      
    InputStream cached = connection.getInputStream();

    //set image if in cache
    myBitmap = BitmapFactory.decodeStream(cached);

} catch (FileNotFoundException e) {
    HttpURLConnection connection2 = (HttpURLConnection) url.openConnection();
    connection2.setDoInput(true);
    connection2.addRequestProperty("Cache-Control", "max-stale=" + 60); 
    connection2.connect();

    InputStream input = connection2.getInputStream();
    myBitmap = BitmapFactory.decodeStream(input);

}

return myBitmap;

} catch (IOException e) {
    e.printStackTrace();        
}

两个问题:

  1. 我设置max-stale=60seconds 用于测试目的。但是,如果我在 5 分钟后调用相同的 URL,它会告诉我,它正在从缓存中加载图像。 我会假设,由于缓存中的 HTTPRequest 已过期,图像被重新加载? 还是我必须自己清理缓存?
  2. 在我的catch 块中,我必须创建第二个HttpURLConnection,因为在打开 URLConnection 后我无法添加属性(这发生在 connection.getInputStream() 中?!)。这是糟糕的编程吗?

毕竟,我发现 HttpResponseCache 的文档记录不佳。我遇到了Volley: Fast Networking,但这似乎记录得更少,即使它提供的正是我需要的东西(请求排队和优先级......)。 您使用什么进行缓存?欢迎提供任何指向库、教程的链接。

更新 我的目标不是低于 4.0 的 Android 版本(其他用户可能仍然感兴趣?)

【问题讨论】:

    标签: android caching httpconnection httpresponsecache


    【解决方案1】:

    HttpResponseCachevolley 的文档记录都很差。然而,我发现你 可以很容易地扩展和调整volley。如果您探索 volley 的源代码,尤其是:CacheEntryCacheDispatcherHttpHeaderParser,您可以看到它是如何实现的。

    CacheEntry 包含serverDateetagttlsofTtl 可以很好地表示缓存状态,它还具有isExpired()refreshNeeded() 方法,方便。

    CacheDispatcher 的实现也很准确:

    // Attempt to retrieve this item from cache.
    Cache.Entry entry = mCache.get(request.getCacheKey());
    
    if (entry == null) {
        request.addMarker("cache-miss");
        // Cache miss; send off to the network dispatcher.
        mNetworkQueue.put(request);
        continue;
    }
    
    // If it is completely expired, just send it to the network.
    if (entry.isExpired()) {
        request.addMarker("cache-hit-expired");
        request.setCacheEntry(entry);
        mNetworkQueue.put(request);
        continue;
    }
    
    // We have a cache hit; parse its data for delivery back to the request.
    request.addMarker("cache-hit");
    Response<?> response = request.parseNetworkResponse(
            new NetworkResponse(entry.data, entry.responseHeaders));
    request.addMarker("cache-hit-parsed");
    
    if (!entry.refreshNeeded()) {
        // Completely unexpired cache hit. Just deliver the response.
        mDelivery.postResponse(request, response);
    } else {
        // Soft-expired cache hit. We can deliver the cached response,
        // but we need to also send the request to the network for
        // refreshing.
        request.addMarker("cache-hit-refresh-needed");
        request.setCacheEntry(entry);
    
        // Mark the response as intermediate.
        response.intermediate = true;
    
        // Post the intermediate response back to the user and have
        // the delivery then forward the request along to the network.
        mDelivery.postResponse(request, response, new Runnable() {
            @Override
            public void run() {
                try {
                    mNetworkQueue.put(request);
                } catch (InterruptedException e) {
                    // Not much we can do about this.
                }
            }
        });
    }
    

    一个有趣的花絮:如果缓存“软过期”,volley 将立即从本地缓存传递数据,并在一段时间后再次从服务器重新传递数据,用于单个请求。

    最后,HttpHeaderParser 尽最大努力应对服务器标头:

    headerValue = headers.get("Date");
    if (headerValue != null) {
        serverDate = parseDateAsEpoch(headerValue);
    }
    
    headerValue = headers.get("Cache-Control");
    if (headerValue != null) {
        hasCacheControl = true;
        String[] tokens = headerValue.split(",");
        for (int i = 0; i < tokens.length; i++) {
            String token = tokens[i].trim();
            if (token.equals("no-cache") || token.equals("no-store")) {
                return null;
            } else if (token.startsWith("max-age=")) {
                try {
                    maxAge = Long.parseLong(token.substring(8));
                } catch (Exception e) {
                }
            } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                maxAge = 0;
            }
        }
    }
    
    headerValue = headers.get("Expires");
    if (headerValue != null) {
        serverExpires = parseDateAsEpoch(headerValue);
    }
    
    serverEtag = headers.get("ETag");
    
    // Cache-Control takes precedence over an Expires header, even if both exist and Expires
    // is more restrictive.
    if (hasCacheControl) {
        softExpire = now + maxAge * 1000;
    } else if (serverDate > 0 && serverExpires >= serverDate) {
        // Default semantic for Expire header in HTTP specification is softExpire.
        softExpire = now + (serverExpires - serverDate);
    }
    
    Cache.Entry entry = new Cache.Entry();
    entry.data = response.data;
    entry.etag = serverEtag;
    entry.softTtl = softExpire;
    entry.ttl = entry.softTtl;
    entry.serverDate = serverDate;
    entry.responseHeaders = headers;
    

    因此,请确保服务器发送正确的标头以及尊重 etag、时间戳和缓存控制标头。

    最后,您可以覆盖 Request 类的 getCacheEntry() 以返回自定义 CacheEntry 使缓存完全根据您的需要运行。

    【讨论】:

      【解决方案2】:

      对不起。但是你为什么不为此使用第三方库呢?尝试为此使用Volley lib。它维护一个开箱即用的缓存,并且它是开箱即用的异步。它真的很好用。 Volley 教程:one (with caching demonstration)two

      还有另一个不错的异步缓存库,适用于 android,具有良好的文档 - Retrofit。这里是Retrofit Caching Example

      here是他们的对比。

      【讨论】:

        【解决方案3】:

        要启用缓存,您只需在应用程序启动时使用以下代码安装 HTTP 响应缓存:

        File httpCacheDir = new File(context.getCacheDir(), "http");
        long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
        HttpResponseCache.install(httpCacheDir, httpCacheSize);
        

        资源是否需要从网络或缓存中获取,由HttpResponseCache 负责。缓存的年龄在资源请求的响应标头中指定。例如此image,指定缓存年龄为 43200 秒。

        您可以使用以下 api 验证资源是从缓存中获取还是从网络中获取:

        关于max-stale,你误解了它的目的。它用于允许过时的缓存响应。这是rfc documentation 的定义:

        表示客户端愿意接受一个响应 超过了过期时间。如果给 max-stale 赋值,那么 客户愿意接受超出其要求的响应 过期时间不超过指定的秒数。如果不 将值分配给 max-stale,则客户端愿意接受 任何年龄的陈旧反应。

        关于缓存控制指令only-if-cached,仅在您的应用程序下载最新内容时需要显示某些内容时使用它。所以不会出现在异常处理程序中处理新的HttpUrlConnection 的问题。来自docs

        有时您会想要显示可用的资源 立即,但并非如此。这可以用于您的应用程序 可以在等待下载最新数据时显示一些东西。 要将请求限制为本地缓存的资源,请添加 only-if-cached 指令

        一个建议,添加finally 块,通过调用disconnect 释放连接。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-02-13
          • 1970-01-01
          • 2012-02-17
          • 1970-01-01
          • 2013-06-25
          • 1970-01-01
          • 2021-12-01
          • 1970-01-01
          相关资源
          最近更新 更多