从使用方法出发,首先是怎么使用,其次是咱们使用的功能在内部是如何实现的,实现方案上有什么技巧,有什么范式。全文基本上是对 OkHttp 源码的一个分析与导读,很是建议你们下载 OkHttp 源码以后,跟着本文,过一遍源码。对于技巧和范式,因为目前个人功力还不到位,分析内容没多少,欢迎你们和我一块儿讨论。java
首先放一张完整流程图(看不懂不要紧,慢慢日后看):git
来自OkHttp 官方网站。github
OkHttpClient client = new OkHttpClient();
复制代码
咦,怎么不见 builder?莫急,且看其构造函数:算法
public OkHttpClient() {
this(new Builder());
}
复制代码
原来是方便咱们使用,提供了一个“快捷操做”,所有使用了默认的配置。OkHttpClient.Builder
类成员不少,后面咱们再慢慢分析,这里先暂时略过:编程
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
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;
}
复制代码
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
复制代码
OkHttpClient
实现了Call.Factory
,负责根据请求建立新的Call
。c#
那咱们如今就来看看它是如何建立 Call 的:缓存
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return new RealCall(this, request);
}
复制代码
如此看来功劳全在RealCall
类了,下面咱们一边分析同步网络请求的过程,一边了解RealCall
的具体内容。bash
咱们首先看RealCall#execute
:服务器
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed"); // (1)
executed = true;
}
try {
client.dispatcher().executed(this); // (2)
Response result = getResponseWithInterceptorChain(); // (3)
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this); // (4)
}
}
复制代码
这里咱们作了 4 件事:cookie
call#clone
方法进行克隆。client.dispatcher().executed(this)
来进行实际执行dispatcher
是刚才看到的OkHttpClient.Builder
的成员之一,它的文档说本身是异步 HTTP 请求的执行策略,如今看来,同步请求它也有掺和。getResponseWithInterceptorChain()
函数获取 HTTP 返回结果,从函数名能够看出,这一步还会进行一系列“拦截”操做。dispatcher
本身已经执行完毕。dispatcher 这里咱们不过分关注,在同步执行的流程中,涉及到 dispatcher 的内容只不过是告知它咱们的执行状态,好比开始执行了(调用executed
),好比执行完毕了(调用finished
),在异步执行流程中它会有更多的参与。
真正发出网络请求,解析返回结果的,仍是getResponseWithInterceptorChain
:
private Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
复制代码
在OkHttp 开发者之一介绍 OkHttp 的文章里面,做者讲到:
the whole thing is just a stack of built-in interceptors.
可见Interceptor
是 OkHttp 最核心的一个东西,不要误觉得它只负责拦截请求进行一些额外的处理(例如 cookie),实际上它把实际的网络请求、缓存、透明压缩等功能都统一了起来,每个功能都只是一个Interceptor
,它们再链接成一个Interceptor.Chain
,环环相扣,最终圆满完成一次网络请求。
从getResponseWithInterceptorChain
函数咱们能够看到Interceptor.Chain
的分布依次是:
OkHttpClient
时设置的interceptors
;RetryAndFollowUpInterceptor
;BridgeInterceptor
;CacheInterceptor
;ConnectInterceptor
;OkHttpClient
时设置的networkInterceptors
;CallServerInterceptor
。在这里,位置决定了功能,最后一个 Interceptor 必定是负责和服务器实际通信的,重定向、缓存等必定是在实际通信以前的。
责任链模式在这个Interceptor
链条中获得了很好的实践。
它包含了一些命令对象和一系列的处理对象,每个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。
对于把Request
变成Response
这件事来讲,每一个Interceptor
均可能完成这件事,因此咱们循着链条让每一个Interceptor
自行决定可否完成任务以及怎么完成任务(自力更生或者交给下一个Interceptor
)。这样一来,完成网络请求这件事就完全从RealCall
类中剥离了出来,简化了各自的责任和逻辑。两个字:优雅!
责任链模式在安卓系统中也有比较典型的实践,例如 view 系统对点击事件(TouchEvent)的处理。
回到 OkHttp,在这里咱们先简单分析一下ConnectInterceptor
和CallServerInterceptor
,看看 OkHttp 是怎么进行和服务器的实际通讯的。
ConnectInterceptor
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
复制代码
实际上创建链接就是建立了一个HttpCodec
对象,它将在后面的步骤中被使用,那它又是何方神圣呢?它是对 HTTP 协议操做的抽象,有两个实现:Http1Codec
和Http2Codec
,顾名思义,它们分别对应 HTTP/1.1 和 HTTP/2 版本的实现。
在Http1Codec
中,它利用Okio对Socket
的读写操做进行封装,Okio 之后有机会再进行分析,如今让咱们对它们保持一个简单地认识:它对java.io
和java.nio
进行了封装,让咱们更便捷高效的进行 IO 操做。
而建立HttpCodec
对象的过程涉及到StreamAllocation
、RealConnection
,代码较长,这里就不展开,这个过程归纳来讲,就是找到一个可用的RealConnection
,再利用RealConnection
的输入输出(BufferedSource
和BufferedSink
)建立HttpCodec
对象,供后续步骤使用。
CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOException {
HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
httpCodec.finishRequest();
Response response = httpCodec.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
// 省略部分检查代码
return response;
}
复制代码
咱们抓住主干部分:
Response
对象;Response
对象;这里咱们能够看到,核心工做都由HttpCodec
对象完成,而HttpCodec
实际上利用的是 Okio,而 Okio 实际上仍是用的Socket
,因此没什么神秘的,只不过一层套一层,层数有点多。
其实Interceptor
的设计也是一种分层的思想,每一个Interceptor
就是一层。为何要套这么多层呢?分层的思想在 TCP/IP 协议中就体现得淋漓尽致,分层简化了每一层的逻辑,每层只须要关注本身的责任(单一原则思想也在此体现),而各层之间经过约定的接口/协议进行合做(面向接口编程思想),共同完成复杂的任务。
简单应该是咱们的终极追求之一,尽管有时为了达成目标不得不复杂,但若是有另外一种更简单的方式,我想应该没有人不肯意替换。
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println(response.body().string());
}
});
// RealCall#enqueue
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
// Dispatcher#enqueue
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
复制代码
这里咱们就能看到 dispatcher 在异步执行时发挥的做用了,若是当前还能执行一个并发请求,那就当即执行,不然加入readyAsyncCalls
队列,而正在执行的请求执行完毕以后,会调用promoteCalls()
函数,来把readyAsyncCalls
队列中的AsyncCall
“提高”为runningAsyncCalls
,并开始执行。
这里的AsyncCall
是RealCall
的一个内部类,它实现了Runnable
,因此能够被提交到ExecutorService
上执行,而它在执行时会调用getResponseWithInterceptorChain()
函数,并把结果经过responseCallback
传递给上层使用者。
这样看来,同步请求和异步请求的原理是同样的,都是在getResponseWithInterceptorChain()
函数中经过Interceptor
链条来实现的网络请求逻辑,而异步则是经过ExecutorService
实现。
在上述同步(Call#execute()
执行以后)或者异步(Callback#onResponse()
回调中)请求完成以后,咱们就能够从Response
对象中获取到响应数据了,包括 HTTP status code,status message,response header,response body 等。这里 body 部分最为特殊,由于服务器返回的数据可能很是大,因此必须经过数据流的方式来进行访问(固然也提供了诸如string()
和bytes()
这样的方法将流内的数据一次性读取完毕),而响应中其余部分则能够随意获取。
响应 body 被封装到ResponseBody
类中,该类主要有两点须要注意:
在2.2.1.2.发送和接收数据:CallServerInterceptor小节中,咱们就看过了 body 相关的代码:
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
复制代码
由HttpCodec#openResponseBody
提供具体 HTTP 协议版本的响应 body,而HttpCodec
则是利用 Okio 实现具体的数据 IO 操做。
这里有一点值得一提,OkHttp 对响应的校验很是严格,HTTP status line 不能有任何杂乱的数据,不然就会抛出异常,在咱们公司项目的实践中,因为服务器的问题,偶尔 status line 会有额外数据,而服务端的问题也毫无头绪,致使咱们不得不忍痛继续使用 HttpUrlConnection,然后者在一些系统上又存在各类其余的问题,例如魅族系统发送 multi-part form 的时候就会出现没有响应的问题。
在2.2.1.同步网络请求小节中,咱们已经看到了Interceptor
的布局,在创建链接、和服务器通信以前,就是CacheInterceptor
,在创建链接以前,咱们检查响应是否已经被缓存、缓存是否可用,若是是则直接返回缓存的数据,不然就进行后面的流程,并在返回以前,把网络的数据写入缓存。
这块代码比较多,但也很直观,主要涉及 HTTP 协议缓存细节的实现,而具体的缓存逻辑 OkHttp 内置封装了一个Cache
类,它利用DiskLruCache
,用磁盘上的有限大小空间进行缓存,按照 LRU 算法进行缓存淘汰,这里也再也不展开。
咱们能够在构造OkHttpClient
时设置Cache
对象,在其构造函数中咱们能够指定目录和缓存大小:
public Cache(File directory, long maxSize);
复制代码
而若是咱们对 OkHttp 内置的Cache
类不满意,咱们能够自行实现InternalCache
接口,在构造OkHttpClient
时进行设置,这样就可使用咱们自定义的缓存策略了。
OkHttp 还有不少细节部分没有在本文展开,例如 HTTP2/HTTPS 的支持等,但创建一个清晰的概览很是重要。对总体有了清晰认识以后,细节部分若有须要,再单独深刻将更加容易。
在文章最后咱们再来回顾一下完整的流程图:
OkHttpClient
实现Call.Factory
,负责为Request
建立Call
;RealCall
为具体的Call
实现,其enqueue()
异步接口经过Dispatcher
利用ExecutorService
实现,而最终进行网络请求时和同步execute()
接口一致,都是经过getResponseWithInterceptorChain()
函数实现;getResponseWithInterceptorChain()
中利用Interceptor
链条,分层实现缓存、透明压缩、网络 IO 等功能;