OkHttp已经是非常流行的android客户端的网络请求框架,我其实在项目中使用也已经好几年了,之前一直把重心放在如何快速的搞定业务上、迭代的效率上,这一点来讲,对于一个公司优秀员工是没有毛病的。但是越到后面,你会发现,真正的高效,是你对技术的更深层次的理解,对技术更熟练的掌握。所以今天重回技术本身,搞清楚OkHttp的实现机制和部分源码分析,也提醒阅读本篇文章的同学,除了在公司加班加点赶业务进度的同时,从长远角度看,提深自我的技术技能,才是对公司和自己的双赢结果。

“OkHttp 4.x upgrades our implementation language from Java to Kotlin and keeps everything else the same. We’ve chosen Kotlin because it gives us powerful new capabilities while integrating closely with Java.”

引入官方文档的一段内容,OkHttp4.x实现语言从Java改成了Kotlin,这里面我们使用的源码是OkHttp3.10,在逻辑实现上并无太多差异。

一、OkHttp的最基础的核心类介绍

选型支撑:

摘自Http官方文档的一段介绍,翻译成了中文如下:

HTTP是现代应用常用的一种交换数据和媒体的网络方式,高效地使用HTTP能让资源加载更快,节省带宽。OkHttp是一个高效的HTTP客户端,它有以下默认特性:

  • 支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接
  • 连接池减少请求延时
  • 透明的GZIP压缩减少响应数据的大小
  • 缓存响应内容,避免一些完全重复的请求

OkHttp的第一个特性,允许同一个主机地址请求共享同一个socket连接,确实OkHttp是直接对于网络分层模型中传输层socket进行封装的,内部实现涉及请求线程池和连接池线程池。

核心类介绍:

从一个简单的Demo入手:

private String url = "http://www.baidu.com";
void okHttpTest() {//核心客户端类OkHttpClient okHttpClient = new OkHttpClient.Builder().build();//构建请求的类Request request = new Request.Builder().url(url).build();//构建调用类Call call = okHttpClient.newCall(request);//同步调用call.execute();//入队列异步调用call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response/*响应核心类*/) throws IOException {}});
}

OkHttp的核心类主要有OkHttpClient,Dispatcher,Call,Request,Response,Interceptor,Chain。除了Dispatcher(分发器)、Intercoptor(拦截器)、Chain(链) 在上述调用代码中没有出现,这三个核心类是OkHttp的底层实现,在之后的源码流程分析中,会一一提及。

OkHttpClient是负责管理多个Call的组织者,Call是由Request和Response组成,Call入队列被执行后的响应回调提供Response对象作为响应。OkHttp执行请求的方式有同步调用和异步调用两种方式,分别是call.excute()进行同步调用,call.enqueue(callBack)进行异步调用。

二、OkHttp原理流程源码分析

我们从上面的demo代码开始,分析一下OkHttp的调用流程以及源码实现,上述代码中:

第一步:OkHttpClient okHttpClient = new OkHttpClient.Builder().build();构建一个OkHttpClient的客户端对象。

第二步:Request request = new Request.Builder().url(url).build();构建请求对象,在请求的对象的构建过程中,会通过Request对象构建者模式的api传入url。

这里OkHttpClient、Request对象的设计,实际是【构建者设计模式】的应用,本篇不对设计模式做详细介绍。

第三步:Call call = okHttpClient.newCall(request);构建了call类对象,传入Request对象,看newCall方法的源码,实际返回的RealCall对象。这里面的目的是准备一个真实的请求对象在未来的某个时间点将被执行。

第四步:异步情况下,通过 call.enqueue(new Callback() {}),完成加入请求队列的操作,等待源码层的实现发起正式的网络请求;同步情况下直接调用call.excute()。

作为应用层开发,完成上述四步流程,一个完整的网络请求所需写的代码就完成了,使用起来还是比较简单的。

接下来,我们继续看call.enqueue(new Callback(){})的内部实现,call的真实对象是RealCall的实例。

2.1OkHttp发起网络请求的两种方式

1、同步请求

同步请求直接调用RealCall.execute()方法,源码如下:

@Override public Response execute() throws IOException {...try {//调用分发器执行client.dispatcher().executed(this);//真正的执行代码在此方法中,最终会返回Response对象,这个方法的核心实现拦截器的相关逻辑Response result = getResponseWithInterceptorChain();if (result == null) throw new IOException("Canceled");return result;}....
}

Dispatcher中的执行代码:

/**Used by Call#execute to signal it is in-flight.*/
synchronized void executed(RealCall call) {//未做任何执行操作,只是添加到运行队列,便于对请求的取消操作进行统一管理runningSyncCalls.add(call);
}

上述代码同步执行,也不涉及线程池。

2、异步请求

看RealCall.enqueue方法的源码:

@Override public void enqueue(Callback responseCallback) {//当前call正在被执行的一个判定synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}captureCallStackTrace();eventListener.callStart(this);//这里实际调用了dispatcher的enqueue方法(小细节 这里在RealCall内部创建Asyncall的对象实例)client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

接下来,看一下dispatcher类的源码:

/*** 源码中注释的解释:Policy on when async requests are executed.* 首先Dispatcher这里被定义为分发器,处理异步请求执行时的策略*/
public final class Dispatcher {//最大请求数private int maxRequests = 64;//同一个host地址的最大请求数private int maxRequestsPerHost = 5;/** Running asynchronous calls. Includes canceled calls that haven't finished yet. *//** 正在执行的任务,包括刚被取消未完成的;这里Deque Double end queue*/private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();/** Ready async calls in the order they'll be run. *//** 准备好将被按顺序执行的call任务*/private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();....synchronized void enqueue(AsyncCall call) {//判定条件入不同的队列if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {runningAsyncCalls.add(call);//使用线程池执行callexecutorService().execute(call);} else {readyAsyncCalls.add(call);}}....
}

runningAsyncCalls:异步任务的运行时队列,readyAsyncCalls:异步任务的等待队列。

runningAsyncCalls.size() < maxRequests 最大请求数<64;同时同一个host地址的最大请求数<5,则把当前call对象添加到运行时队列中,并使用线程池执行call,否则将包装了request的call对象添加到readyAsyncCalls异步等待队列中。这里面的队列是Deque,即Double end queue,是一个双端可插入、移除元素的队列,为什么使用这种数据结构?

之后,使用线程池executorService.execute(call)执行call,前面diam注释中提到过,这里的call是new出来的AsyncCall对象实例。AsyncCall是RealCall的内部类,定义了Request()方法和前面构建的Request对象进行关联。

final class AsyncCall extends NamedRunnable {private final Callback responseCallback;AsyncCall(Callback responseCallback) {super("OkHttp %s", redactedUrl());this.responseCallback = responseCallback;}//orginalRequest,就是在构建RealCall对象的时候,把先前构建的Request对象传入的String host() {return originalRequest.url().host();}Request request() {return originalRequest;}RealCall get() {return RealCall.this;}@Override protected void execute() {boolean signalledCallback = false;try {//真实发起请求的逻辑实现在拦截器中的逻辑中。Response response = getResponseWithInterceptorChain();....} catch (IOException e) {...}} finally {//finished方法是对Dispatcher中运行时队列的移除操作client.dispatcher().finished(this);}}}
}

AsyncCall是一个runable对象,真正发起请求的是在其内部的实现execute方法中。

接下来,继续看一下client.dispatcher.finished()方法的源码实现,源码Dispatcher类中的finished方法。

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {int runningCallsCount;Runnable idleCallback;synchronized (this) {//前面分析到同步运行时任务和异步执行时任务都会添加到Dispatcher的运行时队列中,通过finished方法移除if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");//这个是啥?继续看这个方法的源码了if (promoteCalls) promoteCalls();//异步任务和同步任务正在执行的和runningCallsCount = runningCallsCount();idleCallback = this.idleCallback;}if (runningCallsCount == 0 && idleCallback != null) {idleCallback.run();}
}
finished(runningSyncCalls, call, false)

finished方法的第三个参数,同步执行为false,异步执行为true。参数的含义是指是否需要请求提升,什么意思呢,看源码实现:

public final class Dispatcher {private void promoteCalls() {//请求的最大限制if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.//无等待队列if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.// 从上述英文注释,可以看出“无等待队列中的call需要提升”。// 遍历处理等待队列for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {AsyncCall call = i.next();if (runningCallsForHost(call) < maxRequestsPerHost) {i.remove();//添加到运行队列runningAsyncCalls.add(call);//线程池执行executorService().execute(call);//上述两句代码和我正常发起异步网络请求是一致的,只是call是从等待队列中取出放到运行队列中}if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.}}
}

到此,同步请求或是异步请求,直到分发器的流程就分析完了,但是整体流程还并未分析完成,真正的请求在这里 Response response = getResponseWithInterceptorChain();实现。再继续分析后续流程之前,我们对前面分析过的流程中设计的Dispatcher分发器做一个流程小结。

2.3Dispatcher分发器流程小结

从上述源码流程的分析过程,可以看出分发器接受RealCall调用传递进来的AsyncCall实例,Dispatcher内部设计了两个队列,分别是运行时异步请求队列、等待异步请求队列,Dispatcher控制整体请求的策略和限制,包括最大请求数不超过64、同host请求书不超过5、等待队列call请求对象的promote(从等待异步队列转移到运行时异步队列)。

Dispatcher分发器:管理同步请求和异步请求,对于同步请求只做标记清理作用,对于异步请求同时处理等待队列和运行时队列的转移策略。

小结流程图:

2.2OkHttp中的拦截器相关源码流程分析

在上一小节的源码解析中,我们分析到无论是同步请求还是异步请求,最终的发起请求的逻辑应该在拦截器相关的代码实现中Response response = getResponseWithInterceptorChain();通过此方法的调用,最终返回响应的Response对象实例。我们看一下getResponseWithInterceptorChain()此方法的源码实现。

final class RealCall implements Call {Response getResponseWithInterceptorChain() throws IOException {// 构建一个拦截器的集合List<Interceptor> interceptors = new ArrayList<>();// 向集合中添加通过Client客户端添加的拦截器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 (!forWebSocket) {// 非webSocket添加网络拦截器interceptors.addAll(client.networkInterceptors());}// 添加访问服务的拦截器interceptors.add(new CallServerInterceptor(forWebSocket));Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,originalRequest, this, eventListener, client.connectTimeoutMillis(),client.readTimeoutMillis(), client.writeTimeoutMillis());// 返回拦截器执行后的结果return chain.proceed(originalRequest);}
}

真实的拦截器的执行逻辑,在chain.proceed(originalRequest)的源码实现中

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {...//创建一个新的next chain,实际上就是将当前的拦截器从链中排除RealInterceptorChain next = new RealInterceptorChain(this.interceptors, streamAllocation, httpCodec, connection, this.index + 1, request, this.call, this.eventListener, this.connectTimeout, this.readTimeout, this.writeTimeout);//按照索引取拦截器Interceptor interceptor = (Interceptor)this.interceptors.get(this.index);//执行拦截器的intecept拦截逻辑,并持有next的引用,并继续执行,直到被处理完毕Response response = interceptor.intercept(next);...return response;
}

在上述这个拦截器流程中,实际上是一种【责任链设计模式】的应用,本篇不对设计模式做详细介绍。我们继续来看拦截器的源码实现,在上述代码流程中,涉及到五大默认核心拦截器

1、retryAndFollowUpInterceptor

retryAndFollowUpInterceptor是重试和重定向拦截器,这个OkHttp框架中默认的第一个拦截器,很显然这个拦截器是直接首次接受request的拦截,最终通过责任链式调用的并返回response响应对象。

public Response intercept(Chain chain) throws IOException {...while(!this.canceled) {Response response;try {//责任链式调用response = realChain.proceed(request, streamAllocation, (HttpCodec)null, (RealConnection)null);releaseConnection = false;} catch (RouteException var17) {//路由异常if (!this.recover(...)) {//判定是否能够满足重试条件throw var17.getLastConnectException();}releaseConnection = false;//重试continue;} catch (IOException var18) {//IO异常boolean requestSendStarted = !(var18 instanceof ConnectionShutdownException);if (!this.recover(...)) {//判定是否满足重试条件throw var18;}releaseConnection = false;//重试continue;} finally {.....}....//重定向部分的调用Request followUp = this.followUpRequest(response, streamAllocation.route());if (followUp == null) {if (!this.forWebSocket) {streamAllocation.release();}return response;}....}
}

retryAndFollowUpInterceptor中的intercept的逻辑,还是比较清晰的,通过while循环,然后如果出现路由异常和IO异常,就计划发起重试,在发起重试前会通过!this.recover(…)这个表达式来判定是否满足重试条件。

private boolean recover(IOException e, StreamAllocation streamAllocation, boolean requestSendStarted, Request userRequest) {streamAllocation.streamFailed(e);if (!this.client.retryOnConnectionFailure()) {// 构建OkHttpClient的时候,设置了不允许重试,则失败就结束return false;} else if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) {    // io异常:boolean requestSendStarted =!(var18 instanceof ConnectionShutdownException);// 路由异常,requestSendStarted默认是falsereturn false;} else if (!this.isRecoverable(e, requestSendStarted)) {// 判断是否是可发起重试异常return false;} else {// 判断是否有跟多路由return streamAllocation.hasMoreRoutes();}
}

经过了上面的两个条件判断后,如果走到了第三个分支条件,则需判断是否是可以发起重试的异常,说白了,就是提前预判一下,我发起重试是否有可能管用吗。isRecoverable,啥意思,就是是否是可恢复的。

private boolean isRecoverable(IOException e, boolean requestSendStarted) {//协议异常 无法重试if (e instanceof ProtocolException) {return false;} else if (!(e instanceof InterruptedIOException)) {if (e instanceof SSLHandshakeException && e.getCause() instanceof CertificateException) {// ssl握手证书问题 false,无法重试return false;} else {// ssl 未认证异常return !(e instanceof SSLPeerUnverifiedException);}} else {// socket超时 正常重试,这个也是我们平时最常见看到的重试触发点return e instanceof SocketTimeoutException && !requestSendStarted;}
}

重定向的逻辑实现,因为在我的日常研发中基本没有碰触过,这里也就不细纠了,也确实源码研究的文章篇幅都比较长。到此,retryAndFollowUpInterceptor拦截器的流程就分析完毕了。

小结一下:(因为流程并不复杂就不配流程图了)

retryAndFollowUpInterceptor是请求发起重试和重定向的逻辑,在责任链的调用中,是第一个接受Request对象的首个拦截器,但是其实更重要的也是最终Response响应对象,最终向上层交付的最后一道关。

当后续的责任链式调用的拦截器的结果返回到当前拦截中,如果出现了RouteException、IOException,则会尝试发起可发起的重试,比如socket超时,当然在构建OkHttpClient对象的时候,可以通过retryOnConnectionFailure设置为false,表示不启用框架的重试能力,重定向的逻辑,在重试逻辑之后,根据resoponse对象的响应码,处理相关重试逻辑。

2、BridgeInterceptor

桥拦截器,连接应用程序和服务器的桥梁,我们发出的请求将会经过它的处理才能发给服务器,比如设置请求内容长度,编码,gzip压缩,cookie等,获取响应后保存Cookie等操作。

public Response intercept(Chain chain) throws IOException {Request userRequest = chain.request();Builder requestBuilder = userRequest.newBuilder();RequestBody body = userRequest.body();if (body != null) {MediaType contentType = body.contentType();if (contentType != null) {//设置请求头requestBuilder.header("Content-Type", contentType.toString());}// 是指请求的内容长度long contentLength = body.contentLength();if (contentLength != -1L) {requestBuilder.header("Content-Length", Long.toString(contentLength));requestBuilder.removeHeader("Transfer-Encoding");} else {requestBuilder.header("Transfer-Encoding", "chunked");requestBuilder.removeHeader("Content-Length");}}// 设置请求的主机地址if (userRequest.header("Host") == null) {requestBuilder.header("Host", Util.hostHeader(userRequest.url(), false));}// 设置连接属性if (userRequest.header("Connection") == null) {requestBuilder.header("Connection", "Keep-Alive");}// 设置接收编码boolean transparentGzip = false;if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {transparentGzip = true;requestBuilder.header("Accept-Encoding", "gzip");}// 设置cookieList<Cookie> cookies = this.cookieJar.loadForRequest(userRequest.url());if (!cookies.isEmpty()) {requestBuilder.header("Cookie", this.cookieHeader(cookies));}// 设置请求头的用户代理if (userRequest.header("User-Agent") == null) {requestBuilder.header("User-Agent", Version.userAgent());}Response networkResponse = chain.proceed(requestBuilder.build());// 获取响应头HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()// 处理是否进行gzip解压...                         .....}return responseBuilder.build();
}

这个拦截器的就是对Http请求的头的设置拦截器,这些请求头信息补全,也无法完成和服务建立连接和请求,故被称为客户端发起请求到服务端的桥梁,被称作桥拦截器。

3、CacheInterceptor

缓存拦截器,在日常开发中,也是经常会使用到的一个拦截器,作用是从本地缓存中获取响应数据,并将服务器的数据写入缓存。核心逻辑仍然在intercept()方法中。

public Response intercept(Chain chain) throws IOException {// 读取候选的旧缓存,cacheCandidate,命名确实很好Response cacheCandidate = this.cache != null ? this.cache.get(chain.request()) : null;long now = System.currentTimeMillis();// 解析请求的缓存策略CacheStrategy strategy = (new Factory(now, chain.request(), cacheCandidate)).get();// CacheStrategy的两个成员变量,用于确定缓存的使用策略Request networkRequest = strategy.networkRequest;//如果是null,不使用网络请求Response cacheResponse = strategy.cacheResponse; //如果是null,不用缓存//上述两个成员变量,理论上能组成四种缓存策略,可以直接查看下文中的表格解析....//如果不使用网络请求、而缓存响应也为空,则返回504if (networkRequest == null && cacheResponse == null) {return (new   Builder()).request(...).protocol(Protocol.HTTP_1_1).code(504).message("..")...build();} else if (networkRequest == null) {//不是用网络请求,cacheResponse非空,直接返回缓存响应return cacheResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).build();} else {//networkRequest 不为null,使用网络请求Response networkResponse = null;try {//推进责任链式调用继续执行networkResponse = chain.proceed(networkRequest);} Response response;if (cacheResponse != null) {// 缓存存在if (networkResponse.code() == HTTP_NOT_MODIFIED) {// 响应码为HTTP_NOT_MODIFIED就是304,304是无响应body的,表明缓存有效,直接使用缓存响应response = cacheResponse.newBuilder().headers(combine(cacheResponse.headers(), networkResponse.headers())).sentRequestAtMillis(networkResponse.sentRequestAtMillis()).receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()).cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();networkResponse.body().close();this.cache.trackConditionalCacheHit();this.cache.update(cacheResponse, response);//通过缓存响应构建真实的缓存返回return response;}Util.closeQuietly(cacheResponse.body());}//无缓存匹配情况response = networkResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();if (this.cache != null) { //OKHttpClient创建的时候配置了使用缓存,一般为一个请求第一次发起会出现,配置了缓存无缓存的情况if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {//更新缓存CacheRequest cacheRequest = this.cache.put(response);return this.cacheWritingResponse(cacheRequest, response);}....}return response;}
}

CacheStrategy的两个成员变量组合解析:

NetworkRequest CacheResponse 组合结果说明
null null 既不使用网络,有没有缓存,肯定就没数据返回了,返回504
null not null 不使用网络,缓存非空,就使用缓存
not null null 使用网络请求,缓存为空
not null not null 发起网络请求,返回304(无修改),则直接用缓存的响应,返回200,更新缓存响应并返回(只缓存get请求)

缓存拦截器的主流程,都是在与对缓存策略的处理上,主要搞清楚组合情况就可以了。使用缓存技术也并非OkHttp特有的技术,而是Http的基础。要在应用开发中使用缓存,不仅能够给用户更好的体验,同时也能减小服务器的压力。

故而,在日常开发中,属于get请求属性的请求,我们就是用get请求,这样可以使用我们的Http的缓存策略,曾经的我经历的一个老项目中,所有的app接口发起全是Post请求,这是不规范的,也是性价比不高的方案。

之后,还会再写一篇关于OkHttp的缓存设置内容,相当于补全OkHttp的缓存拦截器这部分的篇幅。

4、ConnectInterceptor
/** 打开一个到目标服务器的连接以及执行下一个拦截器 */
public final class ConnectInterceptor implements Interceptor {public final OkHttpClient client;public ConnectInterceptor(OkHttpClient client) {this.client = client;}@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, chain, doExtensiveHealthChecks);//创建一个链接RealConnection connection = streamAllocation.connection();return realChain.proceed(request, streamAllocation, httpCodec, connection);}
}

直接看源码,连接拦截器的源码部分很简单,但却不简单。

HttpCodec是一个编解码请求和请求和响应的接口,HttpCodec接口的实现分别对应Http1Codec、Http2Codec。httpCodec实例对象是通过streamAllocation.newStream(client, chain, doExtensiveHealthChecks);来获得的。

同时通过streamAllocation获得的是一个RealConnection的对象。

这个拦截器的作用就是打开一个和Server的连接,并HttpCodec、RealConnection实例传递给下一个拦截器CallServerInterceptor,发起执行网络请求,可以理解成ConnectionInterceptor的核心作用就是为真实的网络请求做最直接的准备工作。

关于连接的建立过程,在这一小节不详细分析,会在2.3小节中的连接池模块详细分析。

接下来,就继续看CallServerInterceptor的源码实现。

5、CallServerInterceptor

CallServerInterceptor,调用服务拦截器,正如这个类的文档注释所言,是整个OkHttp框架中的最后一个拦截器,真实发起网络请求的实现就在此拦截的的源码实现中。

/** OkHttp拦截器链条中的最后一个拦截器,真实发起网络请求 */
public final class CallServerInterceptor implements Interceptor {...@Override public Response intercept(Chain chain) throws IOException {...//发送请求头realChain.eventListener().requestHeadersStart(realChain.call());httpCodec.writeRequestHeaders(request);realChain.eventListener().requestHeadersEnd(realChain.call(), request);Response.Builder responseBuilder = null;//请求体的许可(内部实现是非Get、非Header方法,则许可通过))&& 请求的body非空if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {// 如果请求头中是“Expect: 100-continue”,等待HTTP/1.1 响应后,向服务器传输request body请求体数据,如果没有获取服务器的100响应,则直接return 4xx,并不在传输request body请求体数据。if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {//将头部内容请求发送给服务器httpCodec.flushRequest();realChain.eventListener().responseHeadersStart(realChain.call());//读取响应头,正常响应“Expect:100-continue”,即返回的响应码为100,responseBuilder获取的响应头将非空responseBuilder = httpCodec.readResponseHeaders(true);}//正常响应if (responseBuilder == null) {// 头部交互完成后,正常写入请求体realChain.eventListener().requestBodyStart(realChain.call());long contentLength = request.body().contentLength();CountingSink requestBodyOut =new CountingSink(httpCodec.createRequestBody(request, contentLength));BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);// 真实写入请求体部分的源码实现request.body().writeTo(bufferedRequestBody);bufferedRequestBody.close();realChain.eventListener().requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);} else if (!connection.isMultiplexed()) {// 如若 "Expect: 100-continue" 并未返回, 禁止HTTP/1的连接被复用// 除此之外,我们仍然传输请求体给服务是的服务处于一个固定状态。()streamAllocation.noNewStreams();}}// 请求结束httpCodec.finishRequest();// 构建响应头if (responseBuilder == null) {realChain.eventListener().responseHeadersStart(realChain.call());responseBuilder = httpCodec.readResponseHeaders(false);}// 构建响应数据Response response = responseBuilder.request(request).handshake(streamAllocation.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();// 如果这个响应码又是个100int code = response.code();if (code == 100) {// 当我们并发送一个请求,服务端给响应100// 就尝试在读取一遍实际的响应数据responseBuilder = httpCodec.readResponseHeaders(false);response = responseBuilder.request(request).handshake(streamAllocation.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();code = response.code();}realChain.eventListener().responseHeadersEnd(realChain.call(), response);// code 101 情况if (forWebSocket && code == 101) {// 连接升级response = response.newBuilder().body(Util.EMPTY_RESPONSE).build();} else {response = response.newBuilder().body(httpCodec.openResponseBody(response)).build();}...return response;}

CallServerInterceptor,主要是对Http协议本身的封装实现,需要充分的理解这块的源码实现内容,需要对Http的基础知识有所了解。

总结一下CallServerInterceptor的源码实现流程,发送请求头部–>读取非get、非head请求(Post)的响应数据–>结束请求的发送–>读取响应头部–>读取响应数据–>返回封装响应数据。

补充解释:

“Expect: 100-continue”的来龙去脉:(内容来源于腾讯云社区文档)

​ HTTP/1.1 协议里设计 100 (Continue) HTTP 状态码的的目的是,在客户端发送 Request Message 之前,HTTP/1.1 协议允许客户端先判定服务器是否愿意接受客户端发来的消息主体(基于 Request Headers)。

即, Client 和 Server 在Post (较大)数据之前,允许双方“握手”,如果匹配上了,Client 才开始发送(较大)数据。

这么做的原因是,如果客户端直接发送请求数据,但是服务器又将该请求拒绝的话,这种行为将带来很大的资源开销。

协议对 HTTP/1.1 clients 的要求是:

如果 client 预期等待“100-continue”的应答,那么它发的请求必须包含一个 " Expect: 100-continue" 的头域!

到此,整个OkHttp发起一个网络请求的拦截器流程也就分析完了。

三、总结

整个OkHttp框架的源码阅读和分析确实需要花费一些时间,但是即使到本篇文章的末尾,也并未能尽显OkHttp源码中的所有实现,比如整个框架中所涉及到的多种设计模式的应用,缓存模块的设计细节、线程池、连接池设计等。这样会使得篇幅太长,会再用两篇独立的文章继续分析OkHttp源码中的其它细节。

那其实整个OkHttp的实现网络同步请求和异步请求的流程,本篇基本分析完整,整个OkHttp发起请求的核心逻辑实现,本质上都是由拦截器完成的。五大拦截器的核心流程图如下:

发起和响应流程回顾:

真正发起请求的是最后一个拦截器,request对象从第一个拦截器开始一直交付到最后一个CallServerInterceptor,最终在CallServerInterceptor中完成真实的网络请求,对于请求的结果包装成response对象,再逆向返回给上层调用,最终交付给第一个拦截器RetryAndFollowUp拦截器返回。

期待下一篇啦。
获得更多更新,关注gongzhonghao:Hym4Android

OkHttp原理流程源码分析相关推荐

  1. YOLOv3反向传播原理 之 全流程源码分析

    YOLOv3反向传播原理 之 全流程源码分析 1.YOLOv3网络训练中反向传播主体流程 1.1 初始化 1.2 batch内梯度累加 1.3 network和layer 中的关键变量 2.YOLO层 ...

  2. SpringBoot2 | SpringBoot启动流程源码分析(一)

    首页 博客 专栏·视频 下载 论坛 问答 代码 直播 能力认证 高校 会员中心 收藏 动态 消息 创作中心 SpringBoot2 | SpringBoot启动流程源码分析(一) 置顶 张书康 201 ...

  3. 【源码分析】storm拓扑运行全流程源码分析

    [源码分析]storm拓扑运行全流程源码分析 @(STORM)[storm] 源码分析storm拓扑运行全流程源码分析 一拓扑提交流程 一stormpy 1storm jar 2def jar 3ex ...

  4. Activity启动流程源码分析-浅析生命周期函数

    源码分析 接着上一篇 Activity启动流程源码分析-setContentView源码阅读 的讲解,本节介绍一下Activity的生命周期函数何时被调用 要看Activity的生命周期函数何时被调用 ...

  5. nimble源码学习——广播流程源码分析1

    广播流程源码分析1 在controller层有多种状态:广播.空闲.连接等等,这次分析的是广播这个状态或者叫 做角色.在前面controller层循环的分析中,可以明确controller层也有eve ...

  6. Activity启动流程源码分析(基于Android N)

    Activity启动流程源码分析 一个Activity启动分为两种启动方式,一种是从Launcher界面上的图标点击启动,另一种是从一个Activity中设置按钮点击启动另外一个Activity.这里 ...

  7. 二次开发:flowable审批流程实践与创建流程源码分析

    二次开发:flowable审批流程实践与创建流程源码分析 上一篇已经描述了基于开源项目https://doc.iocoder.cn/的flowable的快速开发,创建了一个租户,创建了用户和相应的岗位 ...

  8. springboot MVC视图解析流程源码分析

    Servlet的基础知识 为什么要先了解Servlet的知识呢,因为后面你会看到我们所熟悉的SpringMVC其实也是一个Servlet,只是它封装了很多的东西并和Spring进行了整合,后面我们进行 ...

  9. Spring IOC流程源码分析

    一.Spring 核心容器 IOC初始化过程 Spring 核心之 IOC 容器初体验 IOC 与 DI IOC(Inversion of Control)控制反转:所谓控制反转,就是把原先我们代码里 ...

最新文章

  1. IntelliJ IDEA 最常用配置,应用、永久激活
  2. 分布式存储 Ceph 介绍及原理架构分享--云平台技术栈系列01
  3. Android开发中的小技巧
  4. c#.net常用的小函数和方法集
  5. Linux Kernel TCP/IP Stack — 协议栈收包处理流程
  6. LeetCode题组:第13题-罗马数字转整数
  7. 织梦cms提交熊掌号php,织梦cms熊掌号历史数据提交教程
  8. linux统计字符串频数,Linux 统计某个字符串出现的次数
  9. 赌还是不赌 你应该辞职去创业吗?
  10. python中字符串之间的比较_如何利用python来对比两个字符串的差异
  11. SVN工作笔记002---svn提示上次操作有误,请执行cleanup
  12. 基于python的数据挖掘实验报告_数据挖掘实验报告
  13. HDoj 1862
  14. ctf-2020-12-07
  15. python图像分析_python数字图像处理(一)图像的常见操作
  16. python re 查找字符串中是否含有汉字
  17. 【Android 高性能音频】AAudio 音频库 简介 ( AAudio 音频库简介 | 音频流 | 音频设备 | 共享模式 | 数据模式 )
  18. 智能停车场ARM工控主板应用
  19. 解决百度网盘限速的软件
  20. 能用计算机做什么作文,电脑的用途作文(九篇)

热门文章

  1. 【数据分析】直男带你看京东100页口红数据
  2. 非递归创建排序二叉树
  3. 常用linux手头命令
  4. semiplannar 什么是,QOMX_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka颜色格式
  5. 中国制造业经济继续处于增长周期 未来还需观察
  6. 给手机/平板浏览器上网增加英文外…
  7. linux sublime中文输入法,Linux中Sublime Text 3使用中文输入法
  8. 配置VMware Workstation 主机模式下让虚拟机上网设置详细
  9. 代码review_学习笔记_1
  10. Go 版批量执行命令工具