HttpResponseCache原理分析

摘要

从Android4.0(API 14)开始,SDK源码中新增了一个类:android.net.http.HttpResponseCache.使用这个类可以很方便的对HTTP和HTTPS请求实现cache,所有的缓存逻辑再也不用自己写了,只要你使用HttpURLConnection或者HttpsURLConnection作为默认的网络请求库(也是Google官方建议使用的),底层默认帮你实现的缓存的管理,不支持HttpClient。

Picasso缓存

Picasso提供两级缓存:

内存缓存:

  1. com.squareup.picasso.LruCache

    是仿造android.util.LruCache实现的一种内存缓存的方式。

硬盘缓存:

  1. okhttp3.OkHttpClient
  2. com.squareup.okhttp.OkHttpClient
  3. android.net.http.HttpResponseCache

    调用方式如图:

    Volley缓存

硬盘缓存:

  1. com.android.volley.toolbox.DiskBasedCache

HttpResponseCache原理分析

由于这个功能是在4.0之后的版本才有的,通常我们就可以使用反射的方式来启动响应缓存功能。下面的示例代码展示了如何在Android 4.0及以后的版本中去启用响应缓存的功能,同时还不会影响到之前的版本:

1
2
3
4
5
6
7
8
9
10
private void enableHttpResponseCache() {
try {
long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
File httpCacheDir = new File(getCacheDir(), "http");
Class.forName("android.net.http.HttpResponseCache")
.getMethod("install", File.class, long.class)
.invoke(null, httpCacheDir, httpCacheSize);
} catch (Exception httpResponseCacheNotAvailable) {
}
}

根据自己写的测试页面,也可以使用以下方法,但需要判断系统版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void openCache() {
try {
File httpCacheDir = new File(getCacheDir(), "http");
long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
HttpResponseCache.install(httpCacheDir, httpCacheSize);
} catch (IOException e) {
Log.i("===>", "HTTP response cache installation failed:" + e);
}
}
private void closeCache() {
HttpResponseCache cache = HttpResponseCache.getInstalled();
if (cache != null) {
cache.flush();
}
}

HttpResponseCache#install(File directory, long maxSize)方法根据传入的directory和maxSize生成一个HttpResponseCache,并设置成默认的java.net.ResponseCache。

接下来看调用HttpURLConnection发送请求的时候是如何处理缓存的以及响应结果是如何处理的。

URL#openConnection()

得到HttpURLConnection的方式是调用URL#openConnection()方法。该方法中有一个handler。我们看对handler赋值的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (handler == null) {
try {
if (protocol.equals("file")) {
handler = (URLStreamHandler)Class.
forName("sun.net.www.protocol.file.Handler").newInstance();
} else if (protocol.equals("ftp")) {
handler = (URLStreamHandler)Class.
forName("sun.net.www.protocol.ftp.Handler").newInstance();
} else if (protocol.equals("jar")) {
handler = (URLStreamHandler)Class.
forName("sun.net.www.protocol.jar.Handler").newInstance();
} else if (protocol.equals("http")) {
handler = (URLStreamHandler)Class.
forName("com.android.okhttp.HttpHandler").newInstance();
} else if (protocol.equals("https")) {
handler = (URLStreamHandler)Class.
forName("com.android.okhttp.HttpsHandler").newInstance();
}
} catch (Exception e) {
throw new AssertionError(e);
}
}

可以看到针对HTTP和HTTPS我们使用的是com.android.okhttp.HttpsHandler

1
2
3
4
5
6
@Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
if (url == null || proxy == null) {
throw new IllegalArgumentException("url == null || proxy == null");
}
return new OkHttpClient().setProxy(proxy).open(url);
}

在最新版本OkHttpClient#open(URL url)方法被废弃,我们引入

compile ‘com.squareup.okhttp:okhttp:1.5.0’

查看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
HttpURLConnection open(URL url, Proxy proxy) {
String protocol = url.getProtocol();
OkHttpClient copy = copyWithDefaults();
copy.proxy = proxy;
if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy);
if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy);
throw new IllegalArgumentException("Unexpected protocol: " + protocol);
}
public class HttpURLConnectionImpl extends HttpURLConnection {
.....
}

HttpURLConnectionImpl#connect()

1
2
3
4
5
6
7
@Override public final void connect() throws IOException {
initHttpEngine();
boolean success;
do {
success = execute(false);
} while (!success);
}
HttpURLConnectionImpl#initHttpEngine()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private void initHttpEngine() throws IOException {
if (httpEngineFailure != null) {
throw httpEngineFailure;
} else if (httpEngine != null) {
return;
}
……
// 将newHttpEngine方法的返回值赋值给HttpEngine的成员变量。
httpEngine = newHttpEngine(method, null, null);
} catch (IOException e) {
httpEngineFailure = e;
throw e;
}
}
private HttpEngine newHttpEngine(String method, Connection connection,
RetryableSink requestBody) {
……
// If we're currently not using caches, make sure the engine's client doesn't have one.
OkHttpClient engineClient = client;
if (engineClient.getOkResponseCache() != null && !getUseCaches()) {
engineClient = client.clone().setOkResponseCache(null);
}
// 将之前通过构造函数传递进来的OkHttpClient对象clone一份后再传递给HttpEngine
return new HttpEngine(engineClient, request, bufferRequestBody, connection, null, requestBody);
}
HttpEngine

看到这里我们知道会创建一个HttpEngine类,这个类就是实际在Socket链接上进行数据收发的类。

HttpURLConnectionImpl#execute(boolean readResponse)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Sends a request and optionally reads a response. Returns true if the
* request was successfully executed, and false if the request can be
* retried. Throws an exception if the request failed permanently.
*/
private boolean execute(boolean readResponse) throws IOException {
try {
httpEngine.sendRequest();
route = httpEngine.getRoute();
handshake = httpEngine.getConnection() != null
? httpEngine.getConnection().getHandshake()
: null;
if (readResponse) {
httpEngine.readResponse();
}
return true;
} catch (IOException e) {
……
}
}

主要是httpEngine#readResponse()方法的调用,最终调用到httpEngine#maybeCache(),responseCache#put(cacheableResponse())是最后执行硬盘缓存的调用。这个responseCache就是我们最一开始设置的HttpResponseCache。

1
2
3
4
5
6
7
8
9
10
11
12
13
private void maybeCache() throws IOException {
OkResponseCache responseCache = client.getOkResponseCache();
if (responseCache == null) return;
// Should we cache this response for this request?
if (!CacheStrategy.isCacheable(response, request)) {
responseCache.maybeRemove(request);
return;
}
// Offer this request to the cache.
cacheRequest = responseCache.put(cacheableResponse());
}

参考

  1. Android HttpURLConnection源码分析

  2. HttpResponseCache原理分析