HttpResponseCache不清除旧文件?

时间:2014-05-19 15:22:55

标签: android caching httpconnection httpresponsecache

我将很快处理一个项目,该项目主要使用HTTPRequestsJSON Images,因此我认为考虑缓存是一个好主意。基本上我正在寻找

的解决方案
  1. 以给定的生命周期(f.e.3,6,12小时)
  2. 开始HTTPRequest
  3. 检查,如果该请求在缓存中可用且仍然有效(生命周期)
  4. 如果请求仍然有效,请从缓存中获取,否则请发出请求并保存其响应
  5. 我在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()?!中)。这是不好的编程吗?
    3. 毕竟,我发现HttpResponseCache记录不清。我遇到了Volley: Fast Networking,但这似乎记录得更少,即使它提供了我需要的东西(请求排队和优先级......)。 您使用什么来缓存?欢迎任何指向库,教程的链接。

      更新 我的目标不是针对低于4.0的Android版本(对其他用户来说还是有趣吗?)

3 个答案:

答案 0 :(得分:2)

Soryy。但是为什么不为此使用第三方库?尽量使用Volley lib。它保持开箱即用的缓存,并且开箱即用。它的效果非常好。排球教程:one (with caching demonstration)two

另外还有一个很好的async,为具有良好文档的android缓存lib - Retrofit。这是Retrofit Caching Example

here是他们的比较。

答案 1 :(得分:2)

HttpResponseCachevolley都记录不清。但是,我发现了你 可以非常轻松地扩展和调整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.
            }
        }
    });
}

一个有趣的消息:如果缓存是“软过期”,则凌空将立即从本地缓存传送数据,并在一段时间后再次从服务器重新传送,以用于单个请求。

最后,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;

因此,请确保服务器发送正确的标头以及荣誉标签,时间戳和缓存控制标头。

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

答案 2 :(得分:1)

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

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

是否需要从网络或缓存中提取资源,由HttpResponseCache处理。缓存的时间在资源请求的响应头中指定。例如,此image指定缓存时间为43200秒。

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

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

的定义
  

表示客户端愿意接受具有的响应   超过了到期时间。如果为max-stale分配了一个值,那么   客户愿意接受超过其的回复   到期时间不超过指定的秒数。如果不   将值分配给max-stale,然后客户端愿意接受a   任何年龄的陈旧反应。

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

  

有时您会想要显示资源(如果有的话)   立即,但不是。这可以用于您的应用程序   可以在等待下载最新数据时显示某些内容。   要将请求限制为本地缓存的资源,请添加   only-if-cached指令

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