Fork me on GitHub

安卓面试清单----OKHttp源码解析(二)

上篇文章我们讲到了getResponse()方法,这节接着来看:

getResponse()方法中最重要的有两个方法,sendRequest() 和 readResponse();

先来看 sendRequest() :

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**来自 HttpEngine 类*/
public void sendRequest() throws RequestException, RouteException, IOException {
if(this.cacheStrategy == null) {
if(this.httpStream != null) {
throw new IllegalStateException();
} else {
Request request = this.networkRequest(this.userRequest);
InternalCache responseCache = Internal.instance.internalCache(this.client);
Response cacheCandidate = responseCache != null?responseCache.get(request):null;
long now = System.currentTimeMillis();
this.cacheStrategy = (new Factory(now, request, cacheCandidate)).get();
this.networkRequest = this.cacheStrategy.networkRequest;
this.cacheResponse = this.cacheStrategy.cacheResponse;
if(responseCache != null) {
responseCache.trackResponse(this.cacheStrategy);
}
if(cacheCandidate != null && this.cacheResponse == null) {
Util.closeQuietly(cacheCandidate.body());
}
if(this.networkRequest == null && this.cacheResponse == null) {
this.userResponse = (new Builder()).request(this.userRequest).priorResponse(stripBody(this.priorResponse)).protocol(Protocol.HTTP_1_1).code(504).message("Unsatisfiable Request (only-if-cached)").body(EMPTY_BODY).build();
} else if(this.networkRequest == null) {
this.userResponse = this.cacheResponse.newBuilder().request(this.userRequest).priorResponse(stripBody(this.priorResponse)).cacheResponse(stripBody(this.cacheResponse)).build();
this.userResponse = this.unzip(this.userResponse);
} else {
boolean success = false;
try {
this.httpStream = this.connect();
this.httpStream.setHttpEngine(this);
if(this.writeRequestHeadersEagerly()) {
long contentLength = OkHeaders.contentLength(request);
if(this.bufferRequestBody) {
if(contentLength > 2147483647L) {
throw new IllegalStateException("Use setFixedLengthStreamingMode() or setChunkedStreamingMode() for requests larger than 2 GiB.");
}
if(contentLength != -1L) {
this.httpStream.writeRequestHeaders(this.networkRequest);
this.requestBodyOut = new RetryableSink((int)contentLength);
} else {
this.requestBodyOut = new RetryableSink();
}
} else {
this.httpStream.writeRequestHeaders(this.networkRequest);
this.requestBodyOut = this.httpStream.createRequestBody(this.networkRequest, contentLength);
}
}
success = true;
} finally {
if(!success && cacheCandidate != null) {
Util.closeQuietly(cacheCandidate.body());
}
}
}
}
}
}

第10句:

1
InternalCache responseCache = Internal.instance.internalCache(this.client);

Internal 是个抽象类, Internal.instance 对象最终在 OKhttpClient 的static 代码块中构造,

1
2
3
4
5
6
7
8
9
/**来自 OKhttpClient 类*/
Internal.instance = new Internal() {
......
public InternalCache internalCache(OkHttpClient client) {
return client.internalCache();
}
......
};
1
2
3
4
5
/**来自 OKhttpClient 类*/
InternalCache internalCache() {
return this.cache != null?this.cache.internalCache:this.internalCache;
}

我们这里需要看下cache.internalCache的实现,这个位于Cache.Java里面。

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
/**来自 Cache 类*/
this.internalCache = new InternalCache() {
//根据请求得到响应
public Response get(Request request) throws IOException {
return Cache.this.get(request);
}
//缓存响应
public CacheRequest put(Response response) throws IOException {
return Cache.this.put(response);
}
public void remove(Request request) throws IOException {
Cache.this.remove(request);
}
public void update(Response cached, Response network) throws IOException {
Cache.this.update(cached, network);
}
public void trackConditionalCacheHit() {
Cache.this.trackConditionalCacheHit();
}
public void trackResponse(CacheStrategy cacheStrategy) {
Cache.this.trackResponse(cacheStrategy);
}
};

就是一些对cache的控制,sendRequest方法中的responseCache就是Cache中的InternalCache,因此我们继续看下面的代码。

1
2
3
4
5
/** getResponse 方法*/
Response cacheCandidate = responseCache != null
? responseCache.get(request)
: null;

在看get方法之前我们有必要看一下缓存响应的put方法:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
/**来自 Cache 类*/
private CacheRequest put(Response response) {
//得到请求的方法
String requestMethod = response.request().method();
if (HttpMethod.invalidatesCache(response.request().method())) {
try {
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
//不缓存非GET方法的响应
if (!requestMethod.equals("GET")) {
// Don't cache non-GET responses. We're technically allowed to cache
// HEAD requests and some POST requests, but the complexity of doing
// so is high and the benefit is low.
return null;
}
if (HttpHeaders.hasVaryAll(response)) {
return null;
}
//使用JakeWharton大神的DiskLruCache进行缓存 采用LUR算法
Entry entry = new Entry(response);
DiskLruCache.Editor editor = null;
try {
editor = cache.edit(urlToKey(response.request()));
if (editor == null) {
return null;
}
entry.writeTo(editor);
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}

从上面代码可以看到,首先是对请求的方法进行判断,概括起来就是一句话:只缓存请求方法为GET的响应。然后符合缓存的条件后,使用响应创建一个Entry对象,然后使用DiskLruCache写入缓存,最终返回一个CacheRequestImpl对象。cache是DiskLruCache的实例,调用edit方法传入响应的key值,而key值就是对请求调用urlToKey方法。下面是urlToKey的实现:

1
2
3
4
5
6
/**来自 Cache 类*/
private static String urlToKey(Request request) {
//MD5
return Util.md5Hex(request.url().toString());
}

从代码就可以看出是对请求的URL做MD5然后再得到MD5值的十六进制表示形式,这儿就不继续看了。
Entry实例就是要写入的缓存部分,主要看一下它的writeTo()方法,该方法执行具体的写入磁盘操作:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**来自 Cache 的static内部类 Entry*/
public void writeTo(Editor editor) throws IOException {
BufferedSink sink = Okio.buffer(editor.newSink(0));
sink.writeUtf8(this.url);
sink.writeByte(10);
sink.writeUtf8(this.requestMethod);
sink.writeByte(10);
sink.writeDecimalLong((long)this.varyHeaders.size());
sink.writeByte(10);
int i = 0;
int size;
for(size = this.varyHeaders.size(); i < size; ++i) {
sink.writeUtf8(this.varyHeaders.name(i));
sink.writeUtf8(": ");
sink.writeUtf8(this.varyHeaders.value(i));
sink.writeByte(10);
}
sink.writeUtf8((new StatusLine(this.protocol, this.code, this.message)).toString());
sink.writeByte(10);
sink.writeDecimalLong((long)this.responseHeaders.size());
sink.writeByte(10);
i = 0;
for(size = this.responseHeaders.size(); i < size; ++i) {
sink.writeUtf8(this.responseHeaders.name(i));
sink.writeUtf8(": ");
sink.writeUtf8(this.responseHeaders.value(i));
sink.writeByte(10);
}
if(this.isHttps()) {
sink.writeByte(10);
sink.writeUtf8(this.handshake.cipherSuite().javaName());
sink.writeByte(10);
this.writeCertList(sink, this.handshake.peerCertificates());
this.writeCertList(sink, this.handshake.localCertificates());
if(this.handshake.tlsVersion() != null) {
sink.writeUtf8(this.handshake.tlsVersion().javaName());
sink.writeByte(10);
}
}
sink.close();
}

从上面的代码可以看到,写入缓存的不仅仅只是响应的头部信息,还包括请求的部分信息:URL、请求方法、请求头部。至此,我们看到对于一个请求和响应,缓存中的key值是请求的URL的MD5值,而value包括请求和响应部分。Entry的writeTo()方法只把请求的头部和响应的头部保存了,最关键的响应主体部分在哪里保存呢?答案在put方法的返回体CacheRequestImpl,下面是这个类的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**来自 Cache 的内部类 CacheRequestImpl,实现了CacheRequest接口*/
public CacheRequestImpl(final Editor editor) throws IOException {
this.editor = editor;
this.cacheOut = editor.newSink(1);
this.body = new ForwardingSink(this.cacheOut) {
public void close() throws IOException {
Cache arg0 = Cache.this;
synchronized(Cache.this) {
if(CacheRequestImpl.this.done) {
return;
}
CacheRequestImpl.this.done = true;
Cache.this.writeSuccessCount++;
}
super.close();
editor.commit();
}
};
}

最终,通过 editor.commit();进行缓存写入。看完了put方法再来看get方法就能好理解点了。

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
30
31
/**来自 Cache 类*/
Response get(Request request) {
String key = urlToKey(request);
Snapshot snapshot;
try {
snapshot = this.cache.get(key);
if(snapshot == null) {
return null;
}
} catch (IOException arg6) {
return null;
}
Cache.Entry entry;
try {
entry = new Cache.Entry(snapshot.getSource(0));
} catch (IOException arg5) {
Util.closeQuietly(snapshot);
return null;
}
Response response = entry.response(snapshot);
if(!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
} else {
return response;
}
}

从代码中可以看到,首先是对请求的URL进行MD5计算得到key值,然后尝试根据key值从缓存中得到值,如果没有该值,说明缓存中没有该值,那么直接返回null,否则创建Entry对象,然后再从Entry中得到响应对象,如果请求和响应不匹配(地址,请求方式、请求头) ,那么也返回null,否则就返回响应对象。
下面是Entry的构造方法:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**来自 Cache 类(Entry的构造方法)*/
public Entry(Source in) throws IOException {
try {
BufferedSource source = Okio.buffer(in);
//读请求相关信息
this.url = source.readUtf8LineStrict();
this.requestMethod = source.readUtf8LineStrict();
Builder varyHeadersBuilder = new Builder();
int varyRequestHeaderLineCount = Cache.readInt(source);
for(int statusLine = 0; statusLine < varyRequestHeaderLineCount; ++statusLine) {
varyHeadersBuilder.addLenient(source.readUtf8LineStrict());
}
this.varyHeaders = varyHeadersBuilder.build();
//读响应状态行
StatusLine arg16 = StatusLine.parse(source.readUtf8LineStrict());
this.protocol = arg16.protocol;
this.code = arg16.code;
this.message = arg16.message;
//读响应首部
Builder responseHeadersBuilder = new Builder();
int responseHeaderLineCount = Cache.readInt(source);
for(int blank = 0; blank < responseHeaderLineCount; ++blank) {
responseHeadersBuilder.addLenient(source.readUtf8LineStrict());
}
this.responseHeaders = responseHeadersBuilder.build();
//是HTTPS协议,读握手、证书信息
if(this.isHttps()) {
String arg17 = source.readUtf8LineStrict();
if(arg17.length() > 0) {
throw new IOException("expected \"\" but was \"" + arg17 + "\"");
}
String cipherSuiteString = source.readUtf8LineStrict();
CipherSuite cipherSuite = CipherSuite.forJavaName(cipherSuiteString);
List peerCertificates = this.readCertificateList(source);
List localCertificates = this.readCertificateList(source);
TlsVersion tlsVersion = !source.exhausted()?TlsVersion.forJavaName(source.readUtf8LineStrict()):null;
this.handshake = Handshake.get(tlsVersion, cipherSuite, peerCertificates, localCertificates);
} else {
this.handshake = null;
}
} finally {
in.close();
}
}

在put方法中我们知道了缓存中保存了请求的信息和响应的信息。 获得了包含首部信息的Entry之后,再调用response方法得到正在的响应,下面是response()方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**来自 Cache 内部类 Entry*/
public Response response(Snapshot snapshot) {
String contentType = this.responseHeaders.get("Content-Type");
String contentLength = this.responseHeaders.get("Content-Length");
Request cacheRequest = (new okhttp3.Request.Builder()).
url(this.url).
method(this.requestMethod, (RequestBody)null).
headers(this.varyHeaders).build();
return (new okhttp3.Response.Builder()). request(cacheRequest).
protocol(this.protocol).
code(this.code).
message(this.message).
headers(this.responseHeaders).
body(new Cache.CacheResponseBody(snapshot, contentType, contentLength)).
handshake(this.handshake).build();
}

再看下match方法:

1
2
3
4
5
/**来自 Cache 内部类 Entry*/
public boolean matches(Request request, Response response) {
return this.url.equals(request.url().toString()) && this.requestMethod.equals(request.method()) && OkHeaders.varyMatches(response, this.varyHeaders, request);
}

可以看到,响应的首部信息保存在Entry中,而主体部分是在传入的Snapshot中,主体是创建了一个CacheResponseBody对象。CacheResponseBody继承自ResponseBody类并且使用传入的Snapshot获得put中保存的响应主体部分。

最终通过比较发起请求的url,方法,head等信息和缓存中的进行比较,决定是否返回response。


OK,再回到 HttpEngine 类,上面分析完了10,11行,我们再来接着看13行,

1
this.cacheStrategy = (new Factory(now, request, cacheCandidate)).get();

可以看到根据当前时间、构建的Request请求体、和得到的缓存响应 创建一个工厂,然后再得到一个CacheStrategy。首先看该工厂的构造方法:

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
30
31
32
33
34
35
/**来自 Cache 的内部static工厂类 Factory*/
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
if (cacheResponse != null) {
Headers headers = cacheResponse.headers();
int i = 0;
for (int size = headers.size(); i < size; ++i) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
this.servedDate = HttpDate.parse(value);
this.servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
this.expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
this.lastModified = HttpDate.parse(value);
this.lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
this.etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
this.ageSeconds = HeaderParser.parseSeconds(value, -1);
} else if (OkHeaders.SENT_MILLIS
.equalsIgnoreCase(fieldName)) {
this.sentRequestMillis = Long.parseLong(value);
} else if (OkHeaders.RECEIVED_MILLIS
.equalsIgnoreCase(fieldName)) {
this.receivedResponseMillis = Long.parseLong(value);
}
}
}
}

从代码中可以看出,如果候选的缓存响应不为null,那么将响应首部中有关缓存的首部的值得到,主要有Date、Expires、Last-Modified、ETag和Age首部。

其中Date表明响应报文是何时创建的,Expires表示该响应的绝对过期时间,Last-Modified表示最近一次修改的时间。

再看Factory的get方法:

1
2
3
4
5
6
7
8
9
10
11
12
/**
*来自 Cache 的内部static工厂类 Factory
* Returns a strategy to satisfy {@code request}
* using the a cached response {@code response}.
*/
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
return candidate.networkRequest != null
&& this.request.cacheControl().onlyIfCached() ?
new CacheStrategy((Request) null, (Response) null) : candidate;
}

从代码中可以看出首先调用getCandidate()得到候选的CacheStrategy对象,然后如果得到的缓存策略表明需要使用网络,但是请求中指定响应只能从缓存中得到,那么返回一个networkRequest和cacheResonse均为null的CacheStrategy。

OkHttp中使用了CacheStrategy,它根据之前的缓存结果与当前将要发送Request的header进行策略分析,并得出是否进行请求的结论。

CacheStrategy类似一个mapping操作,将两个值输入,再将两个值输出。


> Input                request, cacheCandidate 
    ↓                        ↓
> CacheStrategy     处理,判断Header信息
    ↓                         ↓  
> Output        networkRequest, cacheResponse

Request:
开发者手动编写并在Interceptor中递归加工而成的对象,我们只需要知道了目前传入的Request中并没有任何关于缓存的Header。

cacheCandidate:
也就是上次与服务器交互缓存的Response,可能为null。我们现在知道它是一个可以读取缓存Header的Response。

当被CacheStrategy加工输出后,输出networkRequest与cacheResponse,根据是否为空执行不同的请求。

图 1 几种情况下的请求策略

下面主要看一下getCandidate方法,该方法返回的策略是基于请求可以使用网络的假设之上的,所以这也就解释了get()方法中为什么要对使用网络但是请求却指定缓存响应的情况做区分。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**来自 Cache 的内部static工厂类 Factory*/
private CacheStrategy getCandidate() {
//如果缓存没有命中(即null),网络请求也不需要加缓存Header了
if (this.cacheResponse == null) {
//`没有缓存的网络请求,查上文的表可知是直接访问
return new CacheStrategy(this.request, (Response) null);
}
// 如果缓存的TLS握手信息丢失,返回进行直接连接
else if (this.request.isHttps()
&& this.cacheResponse.handshake() == null) {
return new CacheStrategy(this.request, (Response) null);
}
//检测response的状态码,Expired时间,是否有no-cache标签
else if (!CacheStrategy.isCacheable(this.cacheResponse,
this.request)) {
return new CacheStrategy(this.request, (Response) null);
} else {
CacheControl requestCaching = this.request.cacheControl();
if (!requestCaching.noCache() && !hasConditions(this.request)) {
//根据RFC协议计算
//计算当前age的时间戳
//now - sent + age (s)
long ageMillis = this.cacheResponseAge();
//大部分情况服务器设置为max-age
long freshMillis = this.computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1) {
//大部分情况下是取max-age
freshMillis = Math.min(freshMillis,
TimeUnit.SECONDS.toMillis((long) requestCaching
.maxAgeSeconds()));
}
long minFreshMillis = 0L;
if (requestCaching.minFreshSeconds() != -1) {
//大部分情况下设置是0
minFreshMillis = TimeUnit.SECONDS
.toMillis((long) requestCaching
.minFreshSeconds());
}
long maxStaleMillis = 0L;
//ParseHeader中的缓存控制信息
CacheControl responseCaching = this.cacheResponse
.cacheControl();
if (!responseCaching.mustRevalidate()
&& requestCaching.maxStaleSeconds() != -1) {
//设置最大过期时间,一般设置为0
maxStaleMillis = TimeUnit.SECONDS
.toMillis((long) requestCaching
.maxStaleSeconds());
}
//缓存在过期时间内,可以使用
//大部分情况下是进行如下判断
//now - sent + age + 0 < max-age + 0
if (!responseCaching.noCache()
&& ageMillis + minFreshMillis < freshMillis
+ maxStaleMillis) {
Builder conditionalRequestBuilder1 = this.cacheResponse
.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
conditionalRequestBuilder1
.addHeader("Warning",
"110 HttpURLConnection \"Response is stale\"");
}
long conditionalRequest1 = 86400000L;
if (ageMillis > conditionalRequest1
&& this.isFreshnessLifetimeHeuristic()) {
conditionalRequestBuilder1
.addHeader("Warning",
"113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy((Request) null,
conditionalRequestBuilder1.build());
} else {
okhttp3.Request.Builder conditionalRequestBuilder = this.request
.newBuilder();
if (this.etag != null) {
conditionalRequestBuilder.header("If-None-Match",
this.etag);
} else if (this.lastModified != null) {
conditionalRequestBuilder.header(
"If-Modified-Since",
this.lastModifiedString);
} else if (this.servedDate != null) {
conditionalRequestBuilder.header(
"If-Modified-Since", this.servedDateString);
}
Request conditionalRequest = conditionalRequestBuilder
.build();
return hasConditions(conditionalRequest) ? new CacheStrategy(
conditionalRequest, this.cacheResponse)
: new CacheStrategy(conditionalRequest,
(Response) null);
}
} else {
//对应相应的iF请求
//如果请求报文使用了`no-cache`标签(这个只可能是开发者故意添加的)
//或者有ETag/Since标签(也就是条件GET请求)
return new CacheStrategy(this.request, (Response) null);
}
}
}
  • 上面的主要内容是缓存,okhttp实现的缓存策略实质上就是大量的if判断集合,这些是根据RFC标准文档写死的。
  • Okhttp的缓存是自动完成的,完全由服务器Header决定的,自己没有必要进行控制。网上热传的文章在Interceptor中手工添加缓存代码控制,它固然有用,但是属于Hack式的利用,违反了RFC文档标准,不建议使用,OkHttp的官方缓存控制在注释中。如果读者的需求是对象持久化,建议用文件储存或者数据库即可(比如realm、litepal)。
  • 服务器的配置非常重要,如果你需要减小请求次数,建议直接找对接人员对max-age等头文件进行优化;服务器的时钟需要严格NTP同步。

总结上面的方法:

  1. 如果缓存没有命中,即cacheResponse==null,那么直接进行网络请求

  2. 如果请求是HTTPS并且缓存响应中没有握手或者握手信息丢失,那么需要重新进行网络请求

  3. 如果响应不应该被存储,那么需要重新进行网络请求 (比如header中指定no-store)

  4. 如果请求中指定不使用缓存响应,那么需要进行网络请求 (比如header中指定no-cache)

  5. 接下来比较缓存响应检查是否有条件请求的首部,如果有,就进行额外的请求( request.header(“If-Modified-Since”) != null || request.header(“If-None-Match”) != null)

上面得到 CacheStrategy 后,我们下来再回到HttpEngine 定位到 17行:

1
2
3
if(responseCache != null) {
responseCache.trackResponse(this.cacheStrategy);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**来自Cache类*/
private synchronized void trackResponse(CacheStrategy cacheStrategy) {
requestCount++;
if (cacheStrategy.networkRequest != null) {
// If this is a conditional request, we'll increment hitCount if/when it hits.
networkCount++;
} else if (cacheStrategy.cacheResponse != null) {
// This response uses the cache and not the network. That's a cache hit.
hitCount++;
}
}

可以看到该方法主要就是对Cache中的三个记录进行赋值,从这儿我们可以得出结论requestCount>=networkCount+hitCount。

当networkRequest和cacheResponse均为null的时候,这个时候的响应既不是从网络得到也不是从缓存得到。 (查阅图1)

cacheResponse表示从缓存上得到的响应。如果该响应没有使用缓存,那么将会为null。

关于Response有一点需要铭记,该类的实例不是一成不变的,响应主体部分只能被消费一次然后关闭,其他参数是不变的。

那么为什么Response的主体部分只能被消费一次呢?

这是因为ResponseBody的底层是用Okio实现的,而Okio的Source只能被读取一次,因为读完之后,Buffer底层的Segment关于之前数据的信息(pos和limit)就丢失了,并且在读完一次之后就将Source关闭了,所以只能读一次。关于Okio的可以参考拆轮子系列:拆 Okio


再用个分割线进行分割,上面的代码得到了缓存策略,即cacheStrategy,再次定位到HttpEngine 的第37行:

1
this.httpStream = this.connect();
1
2
3
4
5
6
/**来自HttpEngine 类*/
private HttpStream connect() throws RouteException, RequestException, IOException {
boolean doExtensiveHealthChecks = !this.networkRequest.method().equals("GET");
return this.streamAllocation.newStream(this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis(), this.client.retryOnConnectionFailure(), doExtensiveHealthChecks);
}

跳转到 newStream 方法,

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
30
/**来自StreamAllocation 类*/
public HttpStream newStream(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws RouteException, IOException {
try {
RealConnection e = this.findHealthyConnection(connectTimeout,
readTimeout, writeTimeout, connectionRetryEnabled,
doExtensiveHealthChecks);
Object resultStream;
if (e.framedConnection != null) {
resultStream = new Http2xStream(this, e.framedConnection);
} else {
e.socket().setSoTimeout(readTimeout);
e.source.timeout().timeout((long) readTimeout,
TimeUnit.MILLISECONDS);
e.sink.timeout().timeout((long) writeTimeout,
TimeUnit.MILLISECONDS);
resultStream = new Http1xStream(this, e.source, e.sink);
}
ConnectionPool arg7 = this.connectionPool;
synchronized (this.connectionPool) {
this.stream = (HttpStream) resultStream;
return (HttpStream) resultStream;
}
} catch (IOException arg10) {
throw new RouteException(arg10);
}
}

在newStream 方法中,首先调用了 findHealthyConnection,得到RealConnection对象,再根据framedConnection 是否为空,确定采取的http协议:
Http2xStream 代表是https请求,采用 http2.0或者spdy 协议。

Http1xStream 代表是http请求,采用 http1.0或者 http1.1 协议。

newStream 方法中又调用了 findHealthyConnection ,
这个方法当中又调用了 findConnection 方法:

1
2
3
4
5
6
7
8
9
10
11
/**来自StreamAllocation 类*/
private RealConnection findHealthyConnection(int connectTimeout,
int readTimeout, int writeTimeout, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException, RouteException {
while (true) {
RealConnection candidate = this.findConnection(connectTimeout,
readTimeout, writeTimeout, connectionRetryEnabled);
......
}
}

我们再来看findConnection 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**来自StreamAllocation 类*/
private RealConnection findConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled)
throws IOException, RouteException {
......
RealConnection pooledConnection = Internal.instance.get(
this.connectionPool, this.address, this);
......
newConnection1.connect(connectTimeout, readTimeout, writeTimeout,
......
Internal.instance.put(this.connectionPool, newConnection1);
return newConnection1;
}

重要的看下这三个方法:

前面我们已经说过了get是Internal的方法,是在OkhttpClient中的static代码块中实现的,这个get方法中最终调用了ConnectPool 的get方法,看名字我们就能猜到,这儿维护了一个连接池,这个东西:

1
private final Deque<RealConnection> connections;

get 和 put 都是对它的操作。

OK,看下第二个方法,connect方法,

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
30
31
32
33
34
35
36
37
38
39
40
41
/**来自 RealConnection 方法,实现了Connection接口*/
public void connect(int connectTimeout, int readTimeout, int writeTimeout, List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
if(this.protocol != null) {
throw new IllegalStateException("already connected");
} else {
RouteException routeException = null;
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
Proxy proxy = this.route.proxy();
Address address = this.route.address();
if(this.route.address().sslSocketFactory() == null && !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException("CLEARTEXT communication not supported: " + connectionSpecs));
} else {
while(this.protocol == null) {
try {
this.rawSocket = proxy.type() != Type.DIRECT && proxy.type() != Type.HTTP?new Socket(proxy):address.socketFactory().createSocket();
this.connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
} catch (IOException arg10) {
Util.closeQuietly(this.socket);
Util.closeQuietly(this.rawSocket);
this.socket = null;
this.rawSocket = null;
this.source = null;
this.sink = null;
this.handshake = null;
this.protocol = null;
if(routeException == null) {
routeException = new RouteException(arg10);
} else {
routeException.addConnectException(arg10);
}
if(!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(arg10)) {
throw routeException;
}
}
}
}
}
}

可以看到,典型的socket连接,最终使用Okio包进行socket连接。

再底层先不看了,了解到主要流程就行。

再次回到httpEngine方法的37行,我们知道connect方法最终得到一个httpstream对象(好像OKhttp2.x版本这个方法没返回值)。
到这儿,sendRequest 方法我们就分析完了。篇幅太长了,所以我们下篇继续来看 readResponse() 方法。