public Builder() {
dispatcher = new Dispatcher();
protocols = OkHttpClient.DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
复制代码
每次建立 okHttpClient 都会建立一个新的 Dispatcher,这里有线程池,若是须要建立多个 okHttpClient 最好传入这个参数,并复用线程池 这个类的主要做用是处理okHttpClient.newCall发送请求的。有异步的请求 enqueue 和同步的请求 executed 还有处理请求结束的 finished(AsyncCall/RealCall)html
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {//总最大请求数和同个 ip 最大请求数的限制
runningAsyncCalls.add(call);
executorService().execute(call);//执行请求
} else {
readyAsyncCalls.add(call);//放到队列中等待调用 finish
}
}
/** 处理异步请求,这里设计到了1个方法4个全局变量,一个个说: @methord executorService() 建立了一个线程池无最大上限,闲置60秒留存0个核心线程 @filed maxRequests 最大同时发起请求的数量默认64个,实际能够经过ExecutorService的maximumPoolSize和BlockingQueue控制,不知道框架为何弄一个这个变量 @filed maxRequestsPerHost每一个ip最大的请求数,默认5个,设计这个字段多是为了不某个服务器同时处理多个请求致使单个请求总时间变长。 @filed runningAsyncCalls若是请求数量未超出maxRequests和maxRequestsPerHost的限制,则加到这个队列里,保存了发送的请求,按照默认的executorService,没有在队列里等待的请求 @filed readyAsyncCalls超出了限制,会加到这个队列里等待。 **/
复制代码
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
/**同步请求newCall.execute会调用到这里,android中这个方法会在子线程调用 @filed runningSyncCalls 这个队列里保存了同步的请求,newCall.execute是有返回值的 **/
复制代码
public synchronized void setIdleCallback(@Nullable Runnable idleCallback) //当全部请求执行完,会调用 idleCallback.run 复制代码
public synchronized void cancelAll() //取消全部的请求,包含异步正在执行的runningAsyncCalls,异步队列中的readyAsyncCalls,同步执行的队列runningSyncCalls。 复制代码
public synchronized List<Call> runningCalls() //获取正在执行的队列 runningAsyncCalls 复制代码
public synchronized List<Call> queuedCalls() //获取等待执行的队列readyAsyncCalls 复制代码
<!--内部处理队列的三个主要方法-->
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls)//当一个call 执行完调用promoteCalls,全部的 call 都执行完调用idleCallback.run private void promoteCalls()//内部调用runningCallsForHost判断是否把readyAsyncCalls中的请求放到runningAsyncCalls private int runningCallsForHost(AsyncCall call)//获取一个ip 下的请求数,在enqueue和promoteCalls中调用判断是否超出maxRequestsPerHost的限制 复制代码
Protocol是一个 enum (HTTP_1_0,HTTP_1_1,HTTP_2,SPDY_3)每次建立一个 OkHttpClient 默认都使用了同一个协议组,默认支持 http/1.1和h2。java
static final List<Protocol> DEFAULT_PROTOCOLS;
DEFAULT_PROTOCOLS = Util.immutableList(new Protocol[]{Protocol.HTTP_2, Protocol.HTTP_1_1});
复制代码
内部实现只有一个构造方法和一个 get 方法android
/**由此能够看出,每一个枚举都会有个 string值**/
Protocol(String protocol) {
this.protocol = protocol;
}
/** 经过一个 string 串解析是哪一种协议类型 **/
public static Protocol get(String protocol) throws IOException {
// Unroll the loop over values() to save an allocation.
if (protocol.equals(HTTP_1_0.protocol)) return HTTP_1_0;
if (protocol.equals(HTTP_1_1.protocol)) return HTTP_1_1;
if (protocol.equals(HTTP_2.protocol)) return HTTP_2;
if (protocol.equals(SPDY_3.protocol)) return SPDY_3;
throw new IOException("Unexpected protocol: " + protocol);
}
复制代码
每次建立一个 OkHttpClient 默认都使用了同一个协议组,https 的安全认证,在 RetryAndFollowUpInterceptor 生成请求的必要参数 createAddress() 中调用,在源码分析2的发送请求流程中具体分析 RetryAndFollowUpInterceptor。 ConnectionSpec 中有三个能够直接使用的常亮 MODERN_TLS 中含有了 tls 1.0, 1.1, 1.2, 1.3和 ssl3; COMPATIBLE_TLS 比 MODERN_TLS多了; CLEARTEXT http 使用,数据不通过加密c++
static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS;
DEFAULT_CONNECTION_SPECS = Util.immutableList(new ConnectionSpec[]{ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT});
复制代码
/**ConnectionSpec.Builder中 保存了变量 @params tls 是否支持数据加密, @params cipherSuites 加密算法类,使用到了类 CipherSuite,定义了一些算法名称字符串常亮。 @params tlsVersions 支持的TLS协议版本 @params supportsTlsExtensions 是否支持扩展协议 **/
public static final class Builder {
boolean tls;
@Nullable String[] cipherSuites;
@Nullable String[] tlsVersions;
boolean supportsTlsExtensions;
......
}
复制代码
public boolean isCompatible(SSLSocket socket) {
if (!tls) {
return false;
}
if (tlsVersions != null && !nonEmptyIntersection(Util.NATURAL_ORDER, tlsVersions, socket.getEnabledProtocols())) {
return false;
}
if (cipherSuites != null && !nonEmptyIntersection(CipherSuite.ORDER_BY_NAME, cipherSuites, socket.getEnabledCipherSuites())) {
return false;
}
return true;
}
/**判断 tlsVersions,cipherSuites是否支持服务器的加密版本和算法类型,若是支持 true 这个方法在类 ConnectionSpecSelector 中调用,因为每一个 OKHttpClient 都有几个 ConnectionSpec,发送请求的时候用 ConnectionSpecSelector 选择一个支持的 ConnectionSpec,在 RealConnection.connectTls() 中调用方法apply 复制代码
/**若是在isCompatible返回了 true,会进到这里,经过supportedSpec()和服务器支持的版本和算法取交集,在设置给 sslSocket *@params sslSocket 发送请求的 socket。在SSLSocketFactory中设置 https://developer.android.com/reference/javax/net/ssl/SSLSocket.html *@params isFallback 是否由于 SSLHandshakeException 或 SSLProtocolException 失败过,RealConnection.connect()中 connectionSpecSelector.connectionFailed(e) 判断异常,若是是上述两个异常会从新链接并isFallback=true **/
void apply(SSLSocket sslSocket, boolean isFallback) {
ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);
if (specToApply.tlsVersions != null) {
sslSocket.setEnabledProtocols(specToApply.tlsVersions);
}
if (specToApply.cipherSuites != null) {
sslSocket.setEnabledCipherSuites(specToApply.cipherSuites);
}
}
复制代码
/**使用intersect方法获取sslSocket和这个 ConnectionSpec 共同支持的tlsVersions,cipherSuites 当 isFallback=true 时,使用 "TLS_FALLBACK_SCSV"解决重试链接失败的问题(SSLv3有漏洞) @return 并 new 一个新的 ConnectionSpec,保证原有数据不被破坏,可是浪费内存 **/
private ConnectionSpec supportedSpec(SSLSocket sslSocket, boolean isFallback) 复制代码
建立 EventListener 的工厂,在 EventListener 中监听各类回调,好比说connect、request,response。每个 call 能够建立一个对应的 EventListener。算法
public interface Factory {//建立 listener 的工厂方法
EventListener create(Call call);
}
/**在类 EventListener 的静态方法factory中 new 了一个内部类 *默认实现的参数 EventListener.NONE 是一个 new EventListener{ } 的空实现 **/
static EventListener.Factory factory(final EventListener listener) {
return new EventListener.Factory() {
public EventListener create(Call call) {
return listener;
}
};
}
复制代码
/**调用 new OkHttpClient().newCall(Request) 中会进入这个方法 *@params eventListener 在每一个 RealCall 中的回调。 **/
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
复制代码
java 中对代理的默认处理,使用了手机厂商的默认实现,整个应用只有一个实例,若是不对手机的代理作特殊处理,这里不要作任何改动。在 RouteSelector 中调用。数据库
private static ProxySelector defaultSelector = new ProxySelectorImpl();//JDK 中的默认实现
public static ProxySelector getDefault() {
return defaultSelector;
}
//若是想修改代理的处理在这里设置一个ProxySelector会影响应用中全部的代理,好比想禁止应用使用代理在 select 方法返回 Proxy.NO_PROXY
public static void setDefault(ProxySelector selector) {
defaultSelector = selector;
}
复制代码
对 cookie 的处理,若是应用有设置 cookie 的要求须要调用 new OkHttpClient.Builder().cookieJar(CookieJar cookieJar) 设置保存和读取 cookie 的实现。框架默认是不保存 cookie 的 NO_COOKIES只对接口作了空实现。设计模式
void saveFromResponse(HttpUrl url, List<Cookie> cookies);
List<Cookie> loadForRequest(HttpUrl url);
复制代码
/** *HttpHeaders的静态方法中调用了 saveFromResponse . *@params HttpUrl保存了请求的地址 *@params Headers里面的数组[key1,value1,key2,value2, ...]保存了请求头的全部键值对. *@methord Cookie.parseAll中调用Cookie.parse解析全部的 Set-Cookie 字段。而后调用CookieJar的saveFromResponse public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) { if (cookieJar == CookieJar.NO_COOKIES) return;//若是不设置 cookie 就不进行解析了,响应速度快 List<Cookie> cookies = Cookie.parseAll(url, headers); if (cookies.isEmpty()) return; cookieJar.saveFromResponse(url, cookies); } 复制代码
//BridgeInterceptor 的 intercept 方法中调用 loadForRequest ,并设置到请求头上。BridgeInterceptor在源码分析1中有详细的讲解
public Response intercept(Chain chain) throws IOException {
......
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
......
}
复制代码
java 中对SocketFactory的默认处理,使用了手机厂商的默认实现,整个应用只有一个实例,若是不对手机的Socket作特殊处理,这里不要作任何改动。数组
private static SocketFactory defaultFactory;
/**和 proxySelector 不一样的是只能 get,不能 set *RetryAndFollowUpInterceptor中把 Socket 设置给了Address *在 RealConnection 使用了address.socketFactory().createSocket() **/
public static synchronized SocketFactory getDefault() {
if (defaultFactory == null) {
defaultFactory = new DefaultSocketFactory();
}
return defaultFactory;
}
复制代码
若是是 https 的请求要验证证书,okhttp 里实现了一套使用 x509 证书格式 的认证。首先 verifyAsIpAddress 判断是 ipAddress 仍是 hostName, 若是是 ip 证书中的 type 是7,若是是 host,证书中的 type 是2,而后分别判断证书中是否包含对应的 ip 或者 host。缓存
/**若是是 ip 经过方法 getSubjectAltNames 读取证书中包含的全部ip。在判断请求的 ip 是其中一个。 *@params X509Certificate 使用getSubjectAlternativeNames方法得到一个数组,数组中每一个条目包含了 type=7 和 ip 或者 type=2和 host。 private boolean verifyIpAddress(String ipAddress, X509Certificate certificate) { List<String> altNames = getSubjectAltNames(certificate, ALT_IPA_NAME); for (int i = 0, size = altNames.size(); i < size; i++) { if (ipAddress.equalsIgnoreCase(altNames.get(i))) { return true; } } return false; } 复制代码
/** *和判断 ip 的相似,这个方法是判断 hostname 的 *@methord verifyHostname 因为证书中存的多是带*号的二级域名,可是 hostname 是一个三级或者四级域名,匹配规则又不能够用正则。就有了这个方法 *下面有一段特殊的判断证书含有 cn 的状况,因为对ssl 不是特别了解,看不懂。 **/
private boolean verifyHostname(String hostname, X509Certificate certificate) {
hostname = hostname.toLowerCase(Locale.US);
boolean hasDns = false;
List<String> altNames = getSubjectAltNames(certificate, ALT_DNS_NAME);
for (int i = 0, size = altNames.size(); i < size; i++) {
hasDns = true;
if (verifyHostname(hostname, altNames.get(i))) {
return true;
}
}
if (!hasDns) {
X500Principal principal = certificate.getSubjectX500Principal();
// RFC 2818 advises using the most specific name for matching.
String cn = new DistinguishedNameParser(principal).findMostSpecific("cn");
if (cn != null) {
return verifyHostname(hostname, cn);
}
}
return false;
}
复制代码
安全性处理,若是客户端发送给服务器的数据有很高的保密要求,不但愿被任何假装服务器接受或者代理层拦截,可使用 new OkHttpClient.Builder().certificatePinner(CertificatePinner certificatePinner) 客户端里的对正式的合法性校验,这个方法在RealConnection的connectTls中调用,当完成 ssl 的握手,拿到服务器的正式。对证书 check,若是证书 check 失败抛出异常。安全
//默认不添加任何限制
public static final CertificatePinner DEFAULT = new Builder().build();
复制代码
/**全部的验证限制在全局变量pins中,这里是一个数组,只要有一个命中则验证经过。 *pin中保存了加密的数据和hostname *@methord findMatchingPins 找到含有 hostname 的 pins。 *@methord sha25六、sha1 至支持这两种算法,对 x509中 publickey 加密,防止 publickey 直接被代码编写者看到 **/
public void check(String hostname, List<Certificate> peerCertificates) throws SSLPeerUnverifiedException {
List<Pin> pins = findMatchingPins(hostname);
if (pins.isEmpty()) return;//若是是空的则正常进行请求
if (certificateChainCleaner != null) {//选定某几个x509证书,由于有可能返回的正式里包含了多个域名的认证
peerCertificates = certificateChainCleaner.clean(peerCertificates, hostname);
}
for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
// Lazily compute the hashes for each certificate.
ByteString sha1 = null;
ByteString sha256 = null;
/**主要的验证逻辑,第一层 for 是证书,第二层 for 是 pin, *若是有任何一个证书和 pin 拼配成功,则能够进行后面的请求, *不然抛出异常AssertionError **/
for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
Pin pin = pins.get(p);
if (pin.hashAlgorithm.equals("sha256/")) {
if (sha256 == null) sha256 = sha256(x509Certificate);
if (pin.hash.equals(sha256)) return; // Success!
} else if (pin.hashAlgorithm.equals("sha1/")) {
if (sha1 == null) sha1 = sha1(x509Certificate);
if (pin.hash.equals(sha1)) return; // Success!
} else {
throw new AssertionError("unsupported hashAlgorithm: " + pin.hashAlgorithm);
}
}
}
//这里官方的注释很到位,只是在抛出异常的时候,打个很详细的 log
// If we couldn't find a matching pin, format a nice exception.
StringBuilder message = new StringBuilder()
.append("Certificate pinning failure!")
.append("\n Peer certificate chain:");
for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
message.append("\n ").append(pin(x509Certificate))
.append(": ").append(x509Certificate.getSubjectDN().getName());
}
message.append("\n Pinned certificates for ").append(hostname).append(":");
for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
Pin pin = pins.get(p);
message.append("\n ").append(pin);
}
throw new SSLPeerUnverifiedException(message.toString());
}
复制代码
proxyAuthenticator,authenticator 都是默认值 NONE,接口中只有一个方法。 authenticator 当服务器返回的响应码是401的时候,须要对服务器进行登陆受权。若是须要继续执行登陆的操做,就复写authenticate,返回一个调用登陆的 request。 proxyAuthenticator当服务器返回的响应码是407的时候,须要对代理服务器进行登陆受权。若是须要继续执行登陆代理服务器的操做,就复写authenticate,返回一个登陆代理的 request
public interface Authenticator {
//返回一个去受权的请求,替换原有请求
Request authenticate(Route route, Response response) throws IOException;
}
Authenticator NONE = new Authenticator() {
@Override public Request authenticate(Route route, Response response) {
return null;
}
};
复制代码
全部方法都在 Internal 中调用,无论有多少个 OkHttpClient,Internal只有一个实例 public static Internal instance;在 OkHttpClient 有一个静态方法块 Internal.instance = new Internal() {...各个方法的实现...},不知道为何这样设计,Internal和工具类没差异。
/**在StreamAllocation.findConnection 中建立了 RealConnection 链接成功后调用put *@field cleanupRunning 若是以前没有请求在执行 cleanupRunning 是 false,调用 put 后赋值 true,而且开始在一个单独的线程执行清理的操做 **/
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
复制代码
/**这里使用了cleanupRunning、cleanupRunnable共同控制了清理线程的执行 *当 put 的时候调用execute而且putcleanupRunning=true,全部的任务执行完cleanup返回了-1,中止清理线程,并cleanupRunning=false,等下次 put并执行execute,若是put的时候有任务在执行不调用execute **/
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {
/** *cleanup返回-1终止线程,大于0的值就等待waitNanos的时间再次执行 **/
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
try {
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
};
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
// Find either a connection to evict, or the time that the next eviction is due.
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
// pruneAndGetAllocationCount方法中清理已经被回收的 StreamAllocation 并返回当前存活的数量
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
idleConnectionCount++;
// 计算空置时间最长的一个 RealConnection 的时长.
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
//上面的代码给4个变量赋值,下面再拿这几个变量计算出改怎样执行清理
if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) {
// 若是超出最大缓存数或者超出最大保活时长,清理掉这个RealConnection
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// 再过 return 的时间就要清理掉这个 connection 了
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// 若是在执行的立刻能执行完,再过 return 的时间就要清理掉这个 connection 了
return keepAliveDurationNs;
} else {
// 没有 connect 了,不在执行清理
return -1;
}
}
closeQuietly(longestIdleConnection.socket());
//走到了第一个 if 中,清理了一个。还须要再次清理一遍
return 0;
}
复制代码
hostname 转 ip 的接口,默认实现Arrays.asList(InetAddress.getAllByName(hostname)),没有什么好说的。
List<InetAddress> lookup(String hostname) throws UnknownHostException;
复制代码
这两个变量控制若是有301,302,303重定向,或者 http 转 https 的接口是否要继续请求重定向后的Location
当使用链接池并无和服务器连通,是否要进行重试。
每一个 okHttpClient 中都有两个list 分别保存了这两个拦截器,在 RealCall 的Response getResponseWithInterceptorChain()
方法中调用 interceptors 在请求发起先加到队列里,再添加错误处理、拼接http协议、缓存处理、创建链接的框架中已有的拦截器这时候已经准备好了,又添加了client.networkInterceptors() 最终写入数据到socket中并获取服务器返回的流,造成了一条数据调用链。
Response getResponseWithInterceptorChain() throws IOException {
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());//OkHttpClient.Builder.addInterceptor(Interceptor interceptor)
interceptors.add(retryAndFollowUpInterceptor);//错误处理,和其余的拦截器相比 这个拦截器先初始化了,这样设计我以为是处理 cancel 用的
interceptors.add(new BridgeInterceptor(client.cookieJar()));//拼接http协议 拼接header和body
interceptors.add(new CacheInterceptor(client.internalCache()));//缓存处理
interceptors.add(new ConnectInterceptor(client));//使用链接池创建链接
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());//OkHttpClient.Builder.addNetworkInterceptor(Interceptor interceptor)
}
interceptors.add(new CallServerInterceptor(forWebSocket));//想服务器发起请求
......
}
复制代码
这两个都是处理缓存的逻辑的,一个是接口,一个是实现类的包装。设置任何一个均可以使用缓存,框架默认是不使用缓存的。在 new CacheInterceptor(client.internalCache()
中使用了InternalCache,优先使用cache,若是没有设置cache才去调用找 InternalCache 个接口。
InternalCache internalCache() {
return cache != null ? cache.internalCache : internalCache;
}
复制代码
Cache中 InternalCache internalCache = new InternalCache(){}
这个变量保存了一个实现类,接口的每一个方法都直接调用了cache的方法,接口中主要方法有put、get、remove、update、和两个计数方法。保存文件的逻辑在DiskLruCache中,这两个类经过Source和Sink传递数据,因为okio把流的读写转换为了Source和Sink这两个接口。因此这样设计有很好的扩展性,涉及到的类有:
用journalFile文件保存了全部缓存的文件key和key对应文件状态READ、DIRTY、CLEAN、REMOVE,有点像管理缓存文件的数据库,这样作的好处是,管理缓存文件是否超过maxSize,文件读写占用时间 判断状态防止读写混乱。
/**把journalFile文件转换为HashMap,文件做为持久化下次打开初始化数据用。计算数据使用HashMap速度快。 *JournalFile有当前使用文件、一个临时文件和一个备份文件 三个 *本次初始化后的请求写到临时文件和hashMap中**/
public synchronized void initialize() throws IOException {
if (initialized) {//防止重复初始化
return; // Already initialized.
}
if (fileSystem.exists(journalFileBackup)) {
// If journal file also exists just delete backup file.
if (fileSystem.exists(journalFile)) {
fileSystem.delete(journalFileBackup);
} else {
fileSystem.rename(journalFileBackup, journalFile);//正式文件丢失,把备份文件恢复
}
}
if (fileSystem.exists(journalFile)) {//正式文件存在
try {
readJournal();//判断文件的有效性,把文件中的每一条读出来添加到HashMap中
processJournal();//才判断是否是超出maxSize,若是超出了要删除多余的文件
initialized = true;
return;
} catch (IOException journalIsCorrupt) {
Platform.get().log(WARN, "DiskLruCache " + directory + " is corrupt: "
+ journalIsCorrupt.getMessage() + ", removing", journalIsCorrupt);
}
try {
delete();
} finally {
closed = false;
}
}
rebuildJournal();//把此次写入的文件命名为临时文件,上次保留的正式文件改成备份文件,上次写入的改成正式文件,
initialized = true;
}
复制代码
completeEdit是保存文件和修改文件名的核心方法,里面没有什么复杂的逻辑,DiskLruCache中比较难理解的就是文件状态处理,只要抓住文件 生成临时-写入数据-重命名或删除 这个顺序的线就容易理解了。
/**当本次请求结束的时候,把dirty文件改成clean状态,标识缓存数据可使用了 *若是请求失败把dirty文件删掉 *而且同时保存到journalFile和hashMap *判断有没有超出最大致积限制并清理**/
completeEdit(Editor editor, boolean success);
复制代码
DiskLruCache.Entry 一个请求要保存为响应体文件,其余数据通过Cache.Entry转换的文件,两个文件。可是读写文件的时间不是瞬间完成的,为了防止产生阻塞,每一个文件都会有clean和dirty两个状态,clean文件能够直接转换为Snapshot.source,因此里面有个方法 snapshot()。
private final class Entry {
final String key;//请求的惟一标识key
final long[] lengths;//cleanFiles文件的长度
final File[] cleanFiles;//数据写入完了的dirtyFiles会被重命名为cleanFiles
final File[] dirtyFiles;//在newSink方法中把这个文件转换为sink,让cache写入数据
boolean readable;//可读状态 journal文件中保存的key是CLEAN ,值为true
Editor currentEditor;//当前编辑这个类的Editor
}
复制代码
DiskLruCache.Snapshot 和Entry一一对应,方便和CacheResponseBody的类传递Source。每一个请求都会有一个对应的 Snapshot,保存了要实例化的Source,和每一个source的对应的文件长度length(计算缓存体积是否超过maxSize,若是每次都读取文件的大小太耗性能了)。
public final class Snapshot implements Closeable {
private final String key;//和entity对应的key
private final Source[] sources;//和entity对应的cleanFiles
private final long[] lengths;//和entity的length一致
}
复制代码
DiskLruCache.Editor 和Entry一一对应,是Entry的操做类,和SharedPreferences.edit是一个做用,把entity转换为流和Cache传递数据,主要方法有两个:
public Source newSource(int index) {
......
return fileSystem.source(entry.cleanFiles[index]);//把cleanFiles文件提供为source读取
......
}
public Sink newSink(int index) {
......
File dirtyFile = entry.dirtyFiles[index];//把dirtyFiles文件提供为sink写入,不是cleanFiles,由于数据存完了会把dirty重命名为clean
sink = fileSystem.sink(dirtyFile);
......
}
复制代码
Cache.Entry 除了响应体的部分实现类和string的相互转换,写入Sink或者读取Source的持久化为文件,主要方法Entry(Source in)根据流初始化,writeTo(DiskLruCache.Editor editor)把里面的数据写入到文件中。 和android.os.Parcelable是很相似的,这样设计是OkHttpClient为全部的java项目设计的不单独为android。大部分代码是按照顺序写入字符串的,就不粘代码了,没什么逻辑
remove只是删除文件的操做,track的两个方法都是打点计数用,并无实质的意义,这里不作分析,只分析put,update,get三个主要方法
CacheRequest put(Response response) {
String requestMethod = response.request().method();
if (HttpMethod.invalidatesCache(response.request().method())) {//这里post方法是不支持缓存的注意了
try {
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
if (!requestMethod.equals("GET")) {//这里post方法是不支持缓存的注意了
return null;
}
if (HttpHeaders.hasVaryAll(response)) {//查找Vary变量,若是里面含有 * 不支持缓存
return null;
}
Entry entry = new Entry(response);
DiskLruCache.Editor editor = null;
try {
editor = cache.edit(key(response.request().url()));//根据response的url生成一个md5获取一个DiskLruCache.Editor,准备写入数据用
if (editor == null) {
return null;
}
entry.writeTo(editor);//把response写入到editor中,这里只写入了header+line
return new CacheRequestImpl(editor);//把response写入到editor中,这里只写入了body,并commit
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}
复制代码
/**这个方法是返回响应码是304的时候调用,只更新头信息不用更新body void update(Response cached, Response network) { Entry entry = new Entry(network); DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot; DiskLruCache.Editor editor = null; try { editor = snapshot.edit(); // Returns null if snapshot is not current. if (editor != null) { entry.writeTo(editor);//没用调用new CacheRequestImpl(editor),只把头信息更新了 editor.commit(); } } catch (IOException e) { abortQuietly(editor); } } 复制代码
Response get(Request request) {
String key = key(request.url());//计算md5
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
snapshot = cache.get(key);//获取保存缓存的流
if (snapshot == null) {
return null;
}
} catch (IOException e) {//当前可能正在写入,不可读,返回空
// Give up because the cache cannot be read.
return null;
}
try {
entry = new Entry(snapshot.getSource(ENTRY_METADATA));//获取header+line
} catch (IOException e) {//能够正常拿到响应信息
Util.closeQuietly(snapshot);
return null;
}
Response response = entry.response(snapshot);//实例化响应体,造成一个完整的response
if (!entry.matches(request, response)) {//判断缓存的请求和法国来的请求是否相同
Util.closeQuietly(response.body());
return null;
}
return response;
}
复制代码
create by dingshaoran