okhttp是现在比较流行的基础网络访问框架,因为有比他更加好用的Retrofit,okhttp用起来确实也很方便,基本可以满足我们日常开发的项目需求,如果实在满足不了,也可以做自己的定制。okhttp的源码也是面试中比较经常问到的,特别是拦截器这部分,好几次都问到了,当时只有很尴尬的回答不知道。作为一名有追求的程序员,也必须了解它的源码。其实好久以前就在看了,只不过当时能力有限,拦截器部分一直看不懂具体干了什么事情,虽然整个大致执行过程还是很容易看懂的,但是一直没写,最近看了下拦截器部分源码,也看了网上很多大牛的博客,感觉总算有点收获,所以打算这段时间写篇关于它的源码总结。
1.基本实现流程
okhttp基本使用流程比较简单,发起异步请求过程
private void testOkHttp() {
OkHttpClient okHttpClient = getOkHttpClient(); //构造 OkHttpClient
Request request = new Request.Builder()
.get() //Method GET
.url("www.baidu.com")
.build(); //构造请求信息
okHttpClient.newCall(request)
.enqueue(new Callback() { //发起异步请求
@Override
public void onResponse(final Call call, final Response response) throws IOException {
//成功拿到响应
int code = response.code();
ResponseBody body = response.body();
//服务器返回的结果信息
String string = body.string();
}
@Override
public void onFailure(final Call call, final IOException e) {
e.printStackTrace();
}
});
}
发起一个请求基本分三步走:
1 .创建okhttpclient对象
2 .通过build模式创建request对象, 封装网络请求参数
3 .okhttpclient.newCall(request)会返回一个realCall对象,然后拿它去发网络请求。
接下来就从基本请求流程,看它内部的执行流程
首先看下okHttpClient.newCall(request)
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
//其实返回了一个RealCall对象
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
内部创建了一个RealCall对象,它是okhttp3.call接口的一个实现
public interface Call extends Cloneable {
//返回这个请求关联的 Request 对象
Request request();
//立即执行请求,阻塞等待拿到响应
Response execute() throws IOException;
//请求入队,异步执行
void enqueue(Callback responseCallback);
//取消一个请求
void cancel();
boolean isExecuted();
boolean isCanceled();
Call clone();
interface Factory {
Call newCall(Request request);
}
}
它的具体实现RealCall表示一个准备好被执行的请求,并且只能被执行一次
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
接着就拿着创建好的RealCall对象去发网络请求,enqueue为异步请求,execute为同步请求
从异步请求开始说起
//RealCall
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));//1
}
//Dispatcher
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
看注释1处responseCallback被封装到AsyncCall中了,然后将结果分发给了Dispatcher的enqueue()方法,在该方法中将call加入队列。
首先看AsyncCall方法实现,它是RealCall的一个内部类
//NameRunnable 是一个runable,它的父接口会执行run()方法,里面会调用execute()方法,也就是AsyncCall的execute()方法。
final class AsyncCall extends NamedRunnable {
...
@Override protected void execute() {
boolean signalledCallback = false;
try {
//通过一个拦截器链去获取一个response
Response response = getResponseWithInterceptorChain();
//是否取消了请求,如果取消了,就会回调onFailure,并返回一个IOExecption异常
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
//请求结果成功就会回调onResponse,并且将response返回回去。
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
...
} finally {
//最后不管结果成功还是失败都会调用Dispatcher的finished去移除队列中的请求
client.dispatcher().finished(this);
}
}
}
注释写的比较清楚了,接着看另一个比较重要的类Dispatcher,它是OkHttp的调度器,负责任务的添加和移除相关操作。
public final class Dispatcher {
private int maxRequests = 64; //同时发送最多64个请求
private int maxRequestsPerHost = 5; //同一host最多发送5个请求
private @Nullable Runnable idleCallback;
private @Nullable ExecutorService executorService; //将要创建异步任务的线程池
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); //等待被执行的异步请求队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); //正在运行的异步任务请求队列(包括已经取消还没有完成的)
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); //正在运行的同步请求队列
//线程池部分的初始化
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
...
}
从Dispatcher调度器可以知道以下信息:
一个OkhttpClient只有一个Dispatcher,也就是说最大请求个数为64
同一host最大请求个数为5个
有三个请求队列保存同步请求,和异步请求
默认线程池核心线程数量为 0,最多数量不限制,消息队列为 SynchronousQueue,因此有请求时会将任务直接提交给线程处理
tip: 关于线程池相关部分,可以看我之前写的线程池文章
接着回到之前Dispatcher.enqueue()这个地方
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
首先会判断正在运行的异步请求个数是否超过64个,并且同一Host的请求个数是否超过5个,如果都不满足就将任务添加进正在运行的队列中,然后添加进线程池,否则将任务添加进准备执行的线程池中。线程池就会执行AsyncCall的run方法,在里面会执行execute方法,这个方法刚刚已经分析了。
异步方法已经看了,然后看下同步请求方法execute()
@Override public Response execute() throws IOException {
synchronized (this) {
//正在执行就会抛异常
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
//将任务添加进调度器的同步请求队列中
client.dispatcher().executed(this);
//通过拦截器链获取获取一个response 和刚刚的一样这是个重点,后面在核心机制里面再去学习
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
//执行完之后就通过finish移除请求对象
client.dispatcher().finished(this);
}
}
同步的看完之后,通过一张图来看下基本执行流程
okhttp一个请求设计几个类,每个类都有自己的职责,这恰恰是我们平时开发应该学习的,对于我们系统中某一个模块,就应该做到责任分明,细化粒度,把具体属于某一阶段的功能、信息、方法都封装到单独的类中,然后逐层添加信息。
2.核心机制学习
基本流程中有个部分没有涉及到,那就是拦截器和拦截器链,接下来就是展示技术的时候了
由于一个网络请求设计的功能和细节非常多,如果它的所有实现都包含在一个类中,那么代码将会异常臃肿,所以okhttp设计者就将单独的功能封装到各自的拦截器中,通过一个拦截器链连接各个拦截器
入口方法是这个getResponseWithInterceptorChain()
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 (!forWebSocket) {
//自定义拦截器
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);
}
tip: 关于两种自定义拦截器的区别部分,可以看这里
拦截器和链的接口都定义在一个类中
public interface Interceptor {
//拦截器需要实现的方法
Response intercept(Chain chain) throws IOException;
interface Chain {
//当前的请求信息
Request request();
//处理请求,返回结果
Response proceed(Request request) throws IOException;
//执行当前请求的连接
@Nullable Connection connection();
Call call();
int connectTimeoutMillis();
Chain withConnectTimeout(int timeout, TimeUnit unit);
int readTimeoutMillis();
Chain withReadTimeout(int timeout, TimeUnit unit);
int writeTimeoutMillis();
Chain withWriteTimeout(int timeout, TimeUnit unit);
}
}
okhttp比较好用同时比较牛的地方就是拦截器和拦截器链的设计,通过一个拦截器链将所有拦截器连接,而我们也可以自定义拦截器去给请求添加Header,也可以去打印响应结果,这是我们在项目中经常干的事情。接下来就看下拦截器的接口实现RealInterceptorChain是如何串联起所有的拦截器。
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors; //重点关注1
private final int index; //重点关注2
...
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
...
// Call the next interceptor in the chain.
//创建下一个拦截器链
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
//拿到当前拦截器List中的拦截器
Interceptor interceptor = interceptors.get(index);
//将下一个拦截器链传入当前拦截器的intercept方法
Response response = interceptor.intercept(next);
...
return response;
}
}
RealInterceptorChain中我们主要做了三件事情
1 .创建了下一个索引的拦截器链,第一个索引默认为0
2 .获取当前索引下面的拦截器
3 .执行当前获取到的拦截器里面的intercept方法,参数为下一个拦截器链。
这里设计得确实神奇,如果文字的不好理解,可以参考拭心大佬的这张图:
每个拦截器内部会执行chain.proceed,这样就可以将上一个和下一个拦截器链连接起来。
整体执行流程大致是这样的:首先创建一个RealInterceptorChain,这个拦截器会传一个索引0,在proceed中接着会获取当前索引下的拦截器,然后索引自加,创建下一个拦截器链,接着执行拦截器的intercept方法,参数为下一个拦截器链的引用,拦截器里面会执行chain.proceed方法,接着就会递归调用下一个拦截器链,最后一个拦截器不会执行chain.proceed,到最后一个执行完就会将response返回,然后依次返回,有点类似aop编程,用皇叔的图比较直观
这部分分析完了,接着主要看看每个拦截器的具体作用
RetryAndFollowUpInterceptor:取消、失败重试、重定向
BridgeInterceptor:把用户请求转换为 HTTP 请求;把 HTTP 响应转换为用户友好的响应
CacheInterceptor:读写缓存、根据策略决定是否使用
ConnectInterceptor:实现和服务器建立连接
CallServerInterceptor:实现读写数据
这几个拦截器掌握清楚了,就基本掌握了okhttp的核心了。
1.) RetryAndFollowUpInterceptor
这个拦截器的实例化是在RealCall中,在这里面
retryAndFollowUpInterceptor.isCanceled()
判断任务是否取消,接着看下intercept方法
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//获取传过来的拦截器链
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
//创建流引用管理类
streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
call, eventListener, callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
//是否取消了请求,如果取消了,则通过streamAllocation释放连接
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
//连接下一个拦截器的方法
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
...
releaseConnection = false;
continue;
} catch (IOException e) {
...
} finally {
//出现了未处理的异常,就会通过streamAllocation释放连接
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// 关联前一个响应
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
//重定向请求
Request followUp = followUpRequest(response);
//不需要重定向时候直接返回结果
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
...
//发生重定向时候url地址会不一样重新创建streamAllocation
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
} else if (streamAllocation.codec() != null) {
...
}
request = followUp;
priorResponse = response;
}
}
主要做了几件事:
1.创建流引用管理类StreamAllocation
2.在一个while循环中调用了realChain.proceed
3.拿到的响应过程如果出现远端和IO异常就continue请求
4.通过followUpRequest重定向请求,如果返回的结果为null,就返回response,否则就会拿着这个request去重新请求,如果重定向的url不一致,会重新创建流引用管理类
接着来看下是如何做重定向请求的
private Request followUpRequest(Response userResponse) throws IOException {
if (userResponse == null) throw new IllegalStateException();
Connection connection = streamAllocation.connection();
Route route = connection != null
? connection.route()
: null;
//获取响应码
int responseCode = userResponse.code();
//获取method
final String method = userResponse.request().method();
//根据不同的code做响应处理
switch (responseCode) {
//代理服务器验证
case HTTP_PROXY_AUTH:
Proxy selectedProxy = route != null
? route.proxy()
: client.proxy();
if (selectedProxy.type() != Proxy.Type.HTTP) {
...
}
return client.proxyAuthenticator().authenticate(route, userResponse);
case HTTP_UNAUTHORIZED:
return client.authenticator().authenticate(route, userResponse);
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// fall-through
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
...
return requestBuilder.url(url).build();
case HTTP_CLIENT_TIMEOUT:
...
return userResponse.request();
default:
return null;
}
}
可以看到会根据不同的code返回不同的request,至于具体是怎样的操作,这里直接借鉴拭心大佬的总结:
如果 code 是 401 or 407 会调用我们构造 OkHttpClient 时传入的 Authenticator,做鉴权处理操作,返回处理后的结果
如果 code 是 307 or 308 重定向,且方法不是 GET 或 HEAD,就返回 null
如果 code 是 300、301、302、303,且构造 OkHttpClient 时设置允许重定向,就从当前响应头中取出 Location 即新地址,然后构造一个新的 Request 再请求一次
如果 code 是 408 超时,且上一次没有超时,就再请求一次
如果 code 是 503 服务不可用,且上一次不是 503,就再请求一次
在这里我们频繁看到 StreamAllocation,它主要用于管理客户端与服务器之间的连接,同时管理连接池,以及请求成功后的连接释放等操作
2 .) BridgeInterceptor
这个拦截器是将用户请求信息转化为HTTP请求信息,把服务器信息转化为用户友好的响应。
来看下它的intercept方法
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
if (body != null) { //根据请求体添加header
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString()); //Content-Type header
}
long contentLength = body.contentLength();
if (contentLength != -1) { //添加不同的header
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", hostHeader(userRequest.url(), false)); //host header
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive"); //connection header
}
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));//加载本地cooker信息
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());//User-Agent header
}
//拦截器链连接方法
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//解压缩处理
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
具体做的事情,请求前:
- 如果这个请求有请求体,就添加 Content-Type, Content-Length 等
- 如果这个请求没有 Host,就通过 url 来获取 Host 值添加到 Header 中
- 如果这个请求没有接收的数据类型 Accept-Encoding,且没指定接收的数据范围,就添加默认接受格式为 gzip
- 去 CookieJar 中根据 url 查询 Cookie 添加到 Header
- 如果当前没有,就添加 User-Agent 信息
发起请求后:
- 如果想要数据的格式是 gzip,就创建 GzipSource 进行解压,同时移除 Content-Encoding 和 Content-Length
这个拦截器的作用就是在用户请求时和响应时添加一些信息
3 .)CacheInterceptor
缓存的作用在http请求中很重要,缓存用得好响应就很快,缓存用得不好就会在对的时间遇见错误的数据,关于缓存的基础理论知识看这篇文章
接下来就看下缓存拦截器是如何实现缓存的,看intercept()方法,代码有点长分段看
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
首先通过cache去获取一个缓存响应,其实它的内部实现是用DiskLruCache来做的,这里不是我们关注的重点,然后通过它去构建一个CacheStrategy对象,这个对象内部会根据用户对当前请求设置的 CacheControl 和缓存响应的时间、ETag 、 LastModified 或者 ServedDate 等 Header 进行判断,最后输出两个值
//加工后拿到两个值,根据这两个值的情况决定是请求网络还是直接返回缓存
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
接下来就是根据输出的这两个对象进行不同的请求
1 . networkRequest为空并且cacheResponse为空,表示调用端只使用缓存,但缓存不可用,就会返回504响应
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
504 Gateway Timeout
作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应。
2 .networkRequest为空,但cacheResponse不为空,就直接返回缓存响应
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
3 .networkRequest不为空,表示不用缓存或者缓存的有效性需要验证,这时候就需要网络了
- 如果 cacheResponse 不为空,且请求的响应码是 304,表示缓存还可以用,就直接返回 cacheResponse
- 否则就返回网络请求的响应
对应的这段逻辑是
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
...
}
// If we have a cache response too, then we're doing a conditional get.
// 缓存不为空,返回的响应码是304
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response 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();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//缓存不可用,就返回新的响应
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
这个就是缓存的几种方式,就是以url的MD5为值从DiskLruCache里找之前的response,然后去CacheStragegy里根据缓存header和过期时间进行判断,决定直接返回结果还是进行请求,在拿到缓存之后,就会将结果保存在cahce里面,具体就不进行得那么深了
4 .) ConnectInterceptor
okhttp底层是通过和服务器建立TCP连接才能通信,但是每次请求都建立连接会极大影响通信效率。HTTP1.1开始允许设置Connection keep-alive,这可以减少下一次TCP/IP三次握手四次挥手的建立,多个请求复用一个连接,提高了性格。看下连接拦截器的实现:
@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);
}
它的实现很简单,但是里面涉及到几个很厉害的类,简单介绍下:
- RealConnection:负责与远程服务器建立连接
- ConnectionPoll:负责连接的管理和清理
- StreamAllocation:负责在一个连接上建立新的流,取消一个请求对应的流,释放完成任务的流等操作,
- HttpCodec:负责请求的写入与响应的读出,即底层 IO
OkHttp 处理一个请求时,会先从连接池中找可以复用的连接,没找到的话就创建新连接, 然后在该连接上创建流对象,通过该流对象完成数据通信,每个类得具体实现也不可能都分析到,毕竟里面得实现还是挺复杂的,想学习的可以看拭心大佬的文章。
5 .) CallServerInterceptor
这是最后一个拦截器,负责实现网络IO,所有拦截器都要依赖它才能拿到响应数据。下面先介绍HttpCodec,因为数据读写都是靠它的,它是一个接口
public interface HttpCodec {
int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
Sink createRequestBody(Request request, long contentLength);
//写入请求头
void writeRequestHeaders(Request request) throws IOException;
// 将请求数据完全flush到底层
void flushRequest() throws IOException;
// 完成请求数据的写入
void finishRequest() throws IOException;
//读取并且解析响应头
Response.Builder readResponseHeaders(boolean expectContinue) throws IOException;
//打开一个读取响应体的流
ResponseBody openResponseBody(Response response) throws IOException;
//关闭流,清理资源
void cancel();
}
它的实现是通过Okio来实现的,它是专门用来读写数据的IO库,主要围绕Source(输入流)和Sink(输出流)来展开扩展的,它不是本篇文章要介绍的,这里就绕过了。
然后来看下拦截器代码的实现
@Override public Response intercept(Chain chain) throws IOException {
...
//将请求的 header 写给服务器
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
//有请求体的话,将body发送给服务器
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
...
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
//写请求体数据
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
...
}
...
//结束请求的发送
httpCodec.finishRequest();
if (responseBuilder == null) {
//读取服务器的响应
responseBuilder = httpCodec.readResponseHeaders(false);
}
//处理响应的结果
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
// 如果Connection值为Close,就关闭连接
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
...
return response;
}
3.总结
写了那么多用一张大神的图给个流程总结,看了okhttp的源码可以看出它的内部设计职责分明,设计得确实神奇,特别是拦截器部分,每个拦截器各自有自己的任务,然后通过拦截器链连接。这种分层此设计思想特别适合以后自己的项目开发中,还是很值的学习的。
4.参考
1.OKHTTP结合官网示例分析两种自定义拦截器的区别
2.深入浅出安卓热门网络框架 OkHttp3 和 Retrofit 原理