我将很快处理一个项目,该项目主要使用HTTPRequests
和JSON
Images
,因此我认为考虑缓存是一个好主意。基本上我正在寻找
HTTPRequest
我在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();
}
两个问题:
max-stale=60seconds
用于测试目的。但是,如果我在5分钟后调用相同的URL,它告诉我,它正在从缓存加载图像。 我认为,由于缓存中的HTTPRequest已过期,因此重新加载了图像?或者我是否必须自己清理缓存? catch
块中,我必须创建第二个HttpURLConnection
,因为在打开URLConnection后我无法添加属性(这发生在connection.getInputStream()?!中)。这是不好的编程吗?毕竟,我发现HttpResponseCache
记录不清。我遇到了Volley: Fast Networking,但这似乎记录得更少,即使它提供了我需要的东西(请求排队和优先级......)。 您使用什么来缓存?欢迎任何指向库,教程的链接。
更新 我的目标不是针对低于4.0的Android版本(对其他用户来说还是有趣吗?)
答案 0 :(得分:2)
Soryy。但是为什么不为此使用第三方库?尽量使用Volley lib。它保持开箱即用的缓存,并且开箱即用。它的效果非常好。排球教程:one (with caching demonstration),two。
另外还有一个很好的async,为具有良好文档的android缓存lib - Retrofit。这是Retrofit Caching Example。
而here是他们的比较。
答案 1 :(得分:2)
HttpResponseCache
和volley
都记录不清。但是,我发现了你
可以非常轻松地扩展和调整volley
。如果您探索排球的源代码,尤其是:CacheEntry
,CacheDispatcher
和HttpHeaderParser
,您可以看到它是如何实现的。
CacheEntry
包含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.
}
}
});
}
一个有趣的消息:如果缓存是“软过期”,则凌空将立即从本地缓存传送数据,并在一段时间后再次从服务器重新传送,以用于单个请求。
最后,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释放连接。