OkHttp3源码阅读理解
Okhttp3作为当下最为流行的网络框架,对于这个框架想必来说是每一个android开发人员都会学习和是使用的吧。为什么要使用OkHttp呢?那我们就来说以下OkHttp的好处吧。
使用OkHttp的好处:
1.支持SPDY,HTTP2.0共享同一个Socket来处理;
2.如果SPDY不可用,则通过连接池来减少请求延时;
3.缓存响应数据来减少重复的网络请求;
4.可以从很多常用的连接问题中自动恢复;
5.使用起来很简单(这也是我们程序员最大的福利吧);
在这里呢,我们就不讲解OkHttp的使用方法了,还是直入主题给大家解析一下OkHttp的源码吧。
一.OkHttp的总体设计和请求流程图
首先就让我们来看一下OkHttp的总体设计吧,这样可能会让我们先对OkHttp有一个大概的了解,如图:
OkHttp的总体框架时使用的构建者模式,创建Request.Builder分发到Dispatcher分发器中,分发器将Request.Builder分发到HttpEngline中,在HttpEngine中首先查看本次请求有没有缓存(Cache),如果有直接从Cache中拿到信息给我们的response,如果没有的话就会把这次请求发送到连接池中(ConnectionPool),在连接池中拿到一个Connection,通过Connection去发送请求,通过路由(Route)和Platform到一个合适的平台,最后通过Server(Socket)获取到数据。(上图主要是旧版的OkHttp的框架,但是并不影响我们的学习,对于新版总体思路也差不多。)
请求流程图如下:
在文章开头就放图出来呢,主要是为让我们的思路清晰一点,也许会有助于我们的理解吧。
代码解析
从请求开始分析
在使用请求时,我们都是通过call的excute()或者enqueue()来实现的。所以我们先从call这儿入手,我们在使用call时都是使用OkHttpClient.newCall()来获取call的实例的,这里就不得不先看看我们的newCall方法是如何实现的了,代码如下:
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
这个方法是不是很简单,实际上就是返回一个RealCall类,我们使用请求时实际上是调用的RealCall的enqueue()或excute();我们来看一下这两个方法的源码实现:
@Override
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.timeoutEnter();
transmitter.callStart();
try {
client.dispatcher().executed(this);
return getResponseWithInterceptorChain();//获取Http返回结果
} finally {
client.dispatcher().finished(this);
}
}
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.callStart();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
从上面的代码不难看出我们的同步和异步请求有一个共同点client.dispatcher().enqueue()或excute()。
所以我们可以得知这个请求最终是通过Dispatcher来完成的。
Dispatcher任务调度
首先看一下Dispatcher类的代码,发现好多注解理解起来应该不难。
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private @Nullable Runnable idleCallback;
/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService;//消费者线程池
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();//将要异步请求的队列
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();//正在运行的异步队列
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();//正在运行的同步队列
//构造方法
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher() {
}
public synchronized ExecutorService executorService() {
if (executorService == null) {//如果不存在线程池
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));//创建默认线程池
}
return executorService;
}
以上代码就是Dispatcher中主要的变量和两个构造方法,在Dispatcher中我们可以设定线程池,如果没有,就会使用默认创建的线程池,这个线程池类似于CachedThreadPool,适合执行大量耗时时间较少的任务。(如果不了解线程池的话建议去看一看,博客一大把,这里就不赘述了)。
1.局部总结一
通过前的源码我们先做一个局部的总结:我们在使用OkHttp请求时实际上是调用的newCall,在newCall中返回RealCall类,所以我们发出excute()或者enqueue()请求时实际上是调用的RealCall的这两个方法,而在这两个方法里最终的实现是通过Dispatcher的excute()或enqueue()来实现的。那么接下来我们查看一下Dispatcher的这两个方法吧。
Dispatcher中的execute()和enqueue():
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
......
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);//将call添加到该队列
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
if (!call.get().forWebSocket) {//若不是WebSocket
AsyncCall existingCall = findExistingCallWithHost(call.host());//在正在运行的队列和将要运行的队列中找是否有连接相同主机的请求。
if (existingCall != null)
//如果有的话,就将existingCall作为参数传入call的reuseCallsPerHostFrom方法中,这个方法在RealCall中
//这么做的原因是,对于异步请求,OkHttp会对着相同的call在请求时进行记数
call.reuseCallsPerHostFrom(existingCall);
}
}
//调用这个方法来推动和执行存放在将要运行的异步队列中的异步请求。
promoteAndExecute();
}
@Nullable
private AsyncCall findExistingCallWithHost(String host) {
for (AsyncCall existingCall : runningAsyncCalls) {
if (existingCall.host().equals(host)) return existingCall;
}
for (AsyncCall existingCall : readyAsyncCalls) {
if (existingCall.host().equals(host)) return existingCall;
}
return null;
}
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();//创建AsyncCall请求集合
boolean isRunning;
synchronized (this) {
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
//如果正在运行的请求达到了最大值,则先退出
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
//如果asyncCall对应的主机数大于等于最大值,就先跳过该请求。
if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
i.remove();
//asyncCall的callsPerHost自增1
asyncCall.callsPerHost().incrementAndGet();
executableCalls.add(asyncCall);//添加到executetableCalls中
runningAsyncCalls.add(asyncCall);//加入runningAsyncCal队列
}
isRunning = runningCallsCount() > 0;
}
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
//通过executorService线程池来执行asyncCall请求。
asyncCall.executeOn(executorService());
}
return isRunning;
}
从上面的代码我们可以看出同步请求方法是很简单的就是将该请求添到正在运行的同步队列中。而异步请求就相对复杂,首先会先将call添加到readyAsyncCalls(将要运行的异步队列)中,注意这个call是AsyncCall类的对象,接下来的操作我们从代码的注释就可以知道了。从代码中看到最终中这个异步请求通过asyncCall.executeOn(executeService())来实现。也就是说对于一个异步请求最终会被包装成AsyncCall,然后通过Dispatcher中的线程池executeService来执行。
接下来查看AsyncCall的executeOn()是如何实现的吧:
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
transmitter.noMoreExchanges(ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
上面代码最主要的就是executorService.execute(this)这一句了,这个方法就是通过线程池来执行当前RealCall,因为RealCall本质上是Runnable,所以这样就切换到子线程中了,就实现了异步。线程池executeService的默认实现在我们文章的开头就有说过了,就是通过Dispatcher的excutorService()方法。看完了这个方法我们回到promoteAndExecute()这个方法中,在这个方法里,最终是通过executorService线程池来执行asyncCall请求,而AsyncCall是作为参数传入线程池的,是RealCall的内部类,也实现了execute方法。
@Override
protected void execute() {
boolean signalledCallback = false;
transmitter.timeoutEnter();
try {
Response response = getResponseWithInterceptorChain();//请求网络
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
从上面代码Response response=getResponseWithTnterceptorChain(forWebSocket),可以看出发出的这个请求返回了Response,明显是请求网络。到这我们的第二阶段关于Dospatcher任务调度算是说完了。但在这里还需要也必要的给大家看看这一个方法,也正是我们上面这段代码的最后一句中的finish(),从代码上看这个方法一定会执行的。
/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
call.callsPerHost().decrementAndGet();
finished(runningAsyncCalls, call);
}
/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(runningSyncCalls, call);
}
private <T> void finished(Deque<T> calls, T call) {
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
idleCallback = this.idleCallback;
}
boolean isRunning = promoteAndExecute();
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
怎么样看到这个方法是不是很熟悉呐,我们在多处看到了这个方法的引用,这个finish方法主要是将此次请求从runningAsyncCalls中移除并在之后执行了promoteAndExecute方法,这个方法用来推动和执行存放在将要运行的异步队列中的异步请求,而上面的第一个重载方法在AsyncCal运行时被调用,第二个重载方法在ReadlCall的execute被调用。这样我们readyAsyncCalls中的请求都会被执行。
局部总结二
使用Dispatcher的异步请求时是很简单的直接加入runningAsyncCalls,而使用enqueue时,是先将其加入到readyAsyncCalls中,让后再符合要求的情况下将其加入到runningAsyncCalls中最后交给线程池处理。而在AsyncCall的execute中最终迈出了请求网络的这一步。
Interceptor拦截器
我们继续往下看
getResponseWithInterceptorChain()方法:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();//创建一个拦截器列表
//添加拦截器
interceptors.addAll(client.interceptors());
//重定向拦截器
interceptors.add(new RetryAndFollowUpInterceptor(client));//对失败请求进行恢复,或者进行重定向。
//桥接拦截器
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//缓存拦截器
interceptors.add(new CacheInterceptor(client.internalCache()));
//连接拦截器
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//如果不是WebSocket请求,则通过networdInterceptors来添加拦截器。
interceptors.addAll(client.networkInterceptors());
}
//读写拦截器
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
在这个方法中我们创建了一个拦截器集合,并且在这个拦截器集合中存入5个最原始的拦截器。这五个拦截器分别是:
1.RetryAndFollowUpInterceptor重定向拦截器
主要是创建一个StreamAllocation,发起请求,请求异常时会重试,根据响应码做重定向和重试,重定向时如果地址不一样会释放连接,另外也保存是否取消的状态值,在重试、请求的到响应后都会判断是否取消。
2.BridgeInterceptor桥接拦截器
在请求之前,根据用户的Request构建一个网路请求对应的request,在请求返回之后,根据网络相应的response构建一个用户response
3.CacheInterceptor缓存拦截器
根据request来判断cache是否有缓存的response,如果有,得到这个response,然后进行判断当前response是否有效,根据request判断缓存的策略,是否要使用网络、缓存、或两者都使用 。
4.ConnectInterceptor连接拦截器
因为TCP每次连接都会建立连接才可以通信,每次连接都会影响通信效率。所以连接拦截器主要是为了复用连接。
5.CallServerInterceptor读写拦截器
负责实现网络IO,所有的拦截器都要依赖它才可以拿到数据。
在上面的方法中就可以看出,由自定义的拦截器和5个原始拦截器组成了我们的拦截器链。构建这样一系列的拦截器兰主要是为了一层层的处理请求,通常情况下,拦截器用来添加、移除、转换请求和响应的头部信息。
拦截器链是使用proceed()方法会根据index取出的目标拦截器来进行处理。
proceed代码
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.exchange != null && !this.exchange.connection().supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.exchange != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (exchange != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
在这个方法中,interceptor.intercept(next)这一句很重要,它说明拦截器就是通过intercept()方法来处理请求的。这个方法是一个抽象方法,在五个最原始的拦截器中都实现了该方法。前面说到拦截器是一层层的执行的,当这一个拦截器执行完毕就会执行拦截器链中的下一个拦截器。一直到所有的拦截器都执行完成后返回一个Response给我们。我们的请求就像一个未加工产品一样,整个拦截器链就像一条流水线一样,一步一步的处理加工的我们的请求,最后返回我们需要的Response。
这就是请求在整个拦截器链中的流程:
参考书籍《Android进阶之光》
参考代码:https://github.com/square/okhttp
我的个人理解。到这里就结束了,欢迎大家指出错误,谢谢