1

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

  1. 从给定的生命周期开始HTTPRequest(fe 3,6,12 小时)
  2. 检查该请求是否在缓存中可用并且仍然有效(生命周期)
  3. 如果 Request 仍然有效,则从 Cache 中取出,否则发出 Request 并保存其 Response

HttpResponseCache在 Android 中找到了课程。它正在工作,但是并没有像我期望的那样工作。

我的测试用例是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 版本(可能仍然对其他用户感兴趣?)

4

3 回答 3

2

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

还有另一个很好的异步缓存库,适用于 android 并具有良好的文档 - Retrofit。这是改造缓存示例

这是他们比较。

于 2014-05-28T22:33:38.613 回答
2

两者HttpResponseCachevolley没有记录。但是,我发现您可以非常轻松地扩展和调整volley. 如果您探索 volley 的源代码,尤其是: 和的源代码CacheEntry,您可以看到它是如何实现的。CacheDispatcherHttpHeaderParser

ACacheEntry持有serverDate, etag,ttl并且sofTtl可以很好地表示缓存状态,并且它具有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、时间戳和缓存控制标头。

最后,您可以重写getCacheEntry()Request以根据您的需要返回自定义CacheEntrymake 缓存的行为。

于 2014-05-29T06:16:05.593 回答
1

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

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

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

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

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

表示客户端愿意接受已超过其过期时间的响应。如果为 max-stale 分配了一个值,则客户端愿意接受已超过其过期时间不超过指定秒数的响应。如果没有为 max-stale 分配任何值,则客户端愿意接受任何年龄的陈旧响应。

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

有时,如果资源立即可用,您会希望显示资源,否则不会。可以使用它,以便您的应用程序可以在等待下载最新数据时显示某些内容。要将请求限制为本地缓存的资源,请添加 only-if-cached 指令

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

于 2014-05-23T04:28:40.683 回答