OkHttp原理流程源码分析
Posted hymKing
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OkHttp原理流程源码分析相关的知识,希望对你有一定的参考价值。
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()
@Override
public void onFailure(Call call, IOException e)
@Override
public 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);
//使用线程池执行call
executorService().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默认是false
return 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 以上是关于OkHttp原理流程源码分析的主要内容,如果未能解决你的问题,请参考以下文章
OkHttp源码中Dispatcher和connectionPool线程池分析