HttpCache in android
github地址:https://github.com/Rowandjj/AndroidHttpCache
概述
http Cache
指的是web浏览器所具有的复用本地已缓存的文档”副本”的能力。我们知道,通过网络获取内容有时候成本很高,因而
缓存和重用以前获取的资源的能力成为优化性能很关键的一个方面。http协议本身提供了缓存的支持。
缓存的优势
1. 减少冗余数据传输
2. 缓解网络带宽瓶颈
3. 降低距离时延
4. 减少服务器负担
http协议对缓存的支持
(2)Response:
注意:
Expires
首部的值是绝对时间,容易受本地时钟偏差影响,而Cache-Control
是相对时间。因而,Cache-Control
的
优先级高于Expires
区分缓存命中还是未命中有点”困难”,因为http没有为用户提供一种手段来区分响应是缓存命中的,还是访问原始服务器得到的。
在这两种情况下,响应码都是200 ok。客户端有一种方法可以判断响应是否来自缓存,就是使用Date首部,将响应中Date首部的值
和当前事件进行比较,如果响应中的日期值比较早,客户端通常就可以认为这是一条缓存的响应,或者客户端也可以通过Age首部来检测
缓存的响应。
android下的httpcache方案
以android提供的HttpResponseCache
为例说明。
我的代码演示了如何使用HttpResponseCache
缓存一张网络图片,NetworkUtils
类提供了getBitmap
和asyncGetBitmap
方法,用于获取
网络图片:
代码1:NetworkUtils#getBitmap()
public static HttpResponse getBitmap(Context context,Uri uri,Policy policy){if(uri == null){throw new IllegalArgumentException("uri is null");}installCacheIfNeeded(context);try {HttpURLConnection connection = (HttpURLConnection) new URL(MainActivity.URL).openConnection();connection.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);connection.setReadTimeout(DEFAULT_READ_TIMEOUT);if(policy == Policy.Cache){connection.setUseCaches(true);}else{connection.setUseCaches(false);connection.addRequestProperty("Cache-Control", "no-cache");connection.addRequestProperty("Cache-Control","max-age=0");}int contentLen = connection.getContentLength();int responseCode = connection.getResponseCode();HttpResponse response = new HttpResponse(responseCode,null,contentLen, BitmapFactory.decodeStream(connection.getInputStream()));connection.getInputStream().close();return response;} catch (IOException e) {e.printStackTrace();}return null;}
这里面我通过客户端的设定Policy
来判断是否需要缓存图片,如果不需要就设置no-cache
头。installCacheIfNeeded
方法
用于初始化缓存,内部实际上调用了HttpCache#install()
方法:
代码2:HttpCache#install()
static HttpResponseCache install(Context context) {if (context == null) {throw new IllegalArgumentException("context must not be null");}File cacheDir = new File(context.getApplicationContext().getCacheDir(), CACHE_DIR);if (!cacheDir.exists()) {cacheDir.mkdirs();}HttpResponseCache cache = null;if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {try {if ((cache = HttpResponseCache.getInstalled()) != null) {return cache;}cache = HttpResponseCache.install(cacheDir, CACHE_SIZE);} catch (IOException e) {e.printStackTrace();Log.e(TAG, "http response installation failed,code = 0");}} else {try {if ((cache = (HttpResponseCache) Class.forName("android.net.http.HttpResponseCache").getMethod("getInstalled").invoke(null)) != null) {return cache;}Method method = Class.forName("android.net.http.HttpResponseCache").getMethod("install", File.class, long.class);cache = (HttpResponseCache) method.invoke(null, cacheDir, CACHE_SIZE);} catch (Exception e) {e.printStackTrace();Log.e(TAG, "http response installation failed,code = 1");}}return cache;}
我在MainActivity中编写了一个测试用例来分别测试了允许缓存和不允许缓存情况下的结果。加载的图片来自阿里cdn图片服务器,
这里的图片的过期时间是一年,所以可以确保能够缓存。
Cache-Control:max-age=31536000
Content-Type:image/jpeg
Date:Thu, 22 Oct 2015 05:41:18 GMT
Expires:Fri, 21 Oct 2016 05:41:18 GMT
测试结果很完美,在允许缓存的情况下,只需要第一次请求server,拿到数据后会缓存到本地预设的目录,以后的请求都会直接走
缓存.
我们来观察下缓存的数据格式,这里我预先设定了缓存目录位于包名/cache/http/
下(相关代码参考HttpCache.java),ddms打开这个
目录,pull出来,发现三个文件,其中有个叫journal的,很显然,这是使用了DiskLruCache
类,另外两个文件有一个大小是185458字节,
肯定是这张图片,另一个文件用sublime打开后内容如下:
http://img.alicdn.com/bao/uploaded/i4/2433767071/TB2hL7dfXXXXXaeXXXXXXXXXXXX_!!2433767071-0-paimai.jpg
GET
0
HTTP/1.1 200 OK
18
Server: Tengine
Content-Type: image/jpeg
Content-Length: 185458
Connection: keep-alive
Date: Thu, 22 Oct 2015 05:41:18 GMT
Last-Modified: Sat, 12 Sep 2015 11:24:37 GMT
Expires: Fri, 21 Oct 2016 05:41:18 GMT
Cache-Control: max-age=31536000
Access-Control-Allow-Origin: *
Via: cache7.l2cm12[0,200-0,H], cache60.l2cm12[16,0], cache1.cn406[0,200-0,H], cache8.cn406[1,0]
Age: 1501380
X-Cache: HIT TCP_MEM_HIT dirn:2:377253066
X-Swift-SaveTime: Sun, 08 Nov 2015 03:15:26 GMT
X-Swift-CacheTime: 30075952
Timing-Allow-Origin: *
X-Android-Sent-Millis: 1446993858366
X-Android-Received-Millis: 1446993858403
X-Android-Response-Source: NETWORK 200
这其实是一个响应头,X-Android-Response-Source
首部是由othttp追加的,可以判断响应数据是从disk cache拿的还是
从网络拿的。代码如下:
代码3:
//返回true说明从缓存拿的
static boolean parseResponseSourceHeader(String header) {if (header == null) {return false;}String[] parts = header.split(" ", 2);if ("CACHE".equals(parts[0])) {return true;}if (parts.length == 1) {return false;}try {return "CONDITIONAL_CACHE".equals(parts[0]) && Integer.parseInt(parts[1]) == 304;} catch (NumberFormatException e) {return false;}}
通过
parseResponseSourceHeader(connection.getHeaderField("X-Android-Response-Source"))
即可判定.
如果使用OkHttp
的话,缓存的控制更加方便,只需创建OkHttpClient
实例的时候设置缓存即可。具体代码可以参考OkHttpUtils
类.
代码4:
try {mHttpClient.setCache(new Cache(new File(context.getApplicationContext().getCacheDir(), CACHE_DIR), CACHE_SIZE));} catch (Exception e) {}
缓存的策略选择可通过CacheControl
来控制。
代码5:
CacheControl cacheControl;if (policy == Policy.Cache) {cacheControl = CacheControl.FORCE_CACHE;} else {CacheControl.Builder builder = new CacheControl.Builder();cacheControl = builder.noCache().build();}
http cache的应用
像volley、picasso等开源库都利用了http缓存来作为DiskCache的方案。具体代码可参见Volley#HttpHeaderParser类以及
Picasso#URLConnectionDownloader类等.
参考资料:
- HTTP协议探索之Cache-Control
- HTTP 缓存
- HttpResponseCache
联系我:
如果有描述错误或不准确的,欢迎微博私信(@楚奕RJ)
标签:
相关文章
-
无相关信息