摘要
从Android4.0(API 14)开始,SDK源码中新增了一个类:android.net.http.HttpResponseCache.使用这个类可以很方便的对HTTP和HTTPS请求实现cache,所有的缓存逻辑再也不用自己写了,只要你使用HttpURLConnection或者HttpsURLConnection作为默认的网络请求库(也是Google官方建议使用的),底层默认帮你实现的缓存的管理,不支持HttpClient。
Picasso缓存
Picasso提供两级缓存:
内存缓存:
com.squareup.picasso.LruCache
是仿造android.util.LruCache实现的一种内存缓存的方式。
硬盘缓存:
- okhttp3.OkHttpClient
- com.squareup.okhttp.OkHttpClient
android.net.http.HttpResponseCache
调用方式如图:
Volley缓存
硬盘缓存:
- 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; 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()); }
|
参考
Android HttpURLConnection源码分析
HttpResponseCache原理分析