synchronized (this) {
if (executed) throw new IllegalStateException(“Already Execu ted”);
// (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 件事:
- 检查这个 call 是否已经被执行了,每个 call 只能被执行一次,如果想要一个完 全一样的 call,可以利用
call#clone
方法进行克隆。- 利用
client.dispatcher().executed(this)
来进行实际执 行 dispatcher 是刚才看到的OkHttpClient.Builder
的成员之一,它的文 档说自己是异步 HTTP 请求的执行策略,现在看来,同步请求它也有掺和。- 调用
getResponseWithInterceptorChain()
函数获取 HTTP 返回结果,从 函数名可以看出,这一步还会进行一系列“拦截”操作。- 最后还要通知
dispatcher
自己已经执行完毕。
dispatcher
这里我们不过度关注,在同步执行的流程中,涉及到 dispatcher
的内容 只不过是告知它我们的执行状态,比如开始执行了(调用 executed
),比如执行 完毕了(调用 finished
),在异步执行流程中它会有更多的参与。
真正发出网络请求,解析返回结果的,还 是 getResponseWithInterceptorChain
:
private Response getResponseWithInterceptorChain() throws IOExce ption {
// Build a full stack of interceptors.
List 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);
}
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
是怎么进 行和服务器的实际通信的。
2.2.1.1.建立连接: ConnectInterceptor
@Override public Response intercept(Chain chain) throws IOExcept ion {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation ();
// We need the network to satisfy this request. Possibly for v alidating a conditional GET. boolean doExtensiveHealthChecks = !request.method().equals(“GE T”);
HttpCodec httpCodec = streamAllocation.newStream(client, doExt ensiveHealthChecks);
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
对象,供后续步骤使用。
2.2.1.2.发送和接收数据: CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOExcept ion {
HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStrea m();
StreamAllocation streamAllocation = ((RealInterceptorChain) ch ain).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, r equest.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(re
questBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
…
// 省略部分检查代码
return response;
}
我们抓住主干部分:
- 向服务器发送 request header;
- 如果有 request body,就向服务器发送;
- 读取 response header,先构造一个 Response 对象;
- 如果有 response body,就在 3 的基础上加上 body 构造一个新 的
Response
对象;
这里我们可以看到,核心工作都由 HttpCodec
对象完成,而 HttpCodec
实际上 利用的是 Okio
,而 Okio
实际上还是用的 Socket ,所以没什么神秘的,只不过一 层套一层,层数有点多。
其实 Interceptor
的设计也是一种分层的思想,每个 Interceptor
就是一层。 为什么要套这么多层呢?分层的思想在 TCP/IP
协议中就体现得淋漓尽致,分层简 化了每一层的逻辑,每层只需要关注自己的责任(单一原则思想也在此体现),而 各层之间通过约定的接口/协议进行合作(面向接口编程思想),共同完成复杂的任 务
简单应该是我们的终极追求之一,尽管有时为了达成目标不得不复杂,但如果有另 一种更简单的方式,我想应该没有人不愿意替换。
2.2.2.发起异步网络请求
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 Execu ted”);
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
// Dispatcher#enqueue
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForH ost(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
实现。
2.3返回数据的获取
在上述同步( Call#execute()
执行之后)或者异步 ( Callback#onResponse()
回调中)请求完成之后,我们就可以 从 Response
对象中获取到响应数据了,包括 HTTP status code,status message,response header,response body
等。这里 body 部分最为特殊,因为 服务器返回的数据可能非常大,所以必须通过数据流的方式来进行访问(当然也提 供了诸如 string()
和 bytes()
这样的方法将流内的数据一次性读取完毕),而 响应中其他部分则可以随意获取。
响应 body 被封装到 ResponseBody
类中,该类主要有两点需要注意:
- 每个 body 只能被消费一次,多次消费会抛出异常;
- body 必须被关闭,否则会发生资源泄漏;
在2.2.1.2.发送和接收数据:CallServerInterceptor
小节中,我们就看过了 body 相 关的代码:
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
生资源泄漏;
在2.2.1.2.发送和接收数据:CallServerInterceptor
小节中,我们就看过了 body 相 关的代码:
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()