okHttp3 源码分析
一, 前言
在上一篇博客OkHttp3 使用详解里,我们已经介绍了 OkHttp 发送同步请求和异步请求的基本使用方法。
OkHttp 提交网络请求需要经过这样四个步骤:
- 初始化 OkHttpClient
- 创建 Request
- 创建 Call 对象(okHttpClient.newCall(request))
- 通过 call.excute 来发送同步请求,通过 call. enqueue 来发送异步请求
二,源码分析
虽然 OkHttp 发送同步和异步请求的步骤非常类似,但是实际上它们之间的内部实现是有很大区别的。
下面根据 OkHttp 提交请求的这四个步骤来具体来分析一下 OkHttp 的内部流程
1.1 同步请求的流程源码分析
OkHttpClient
首先看一下 OkHttpClient 的构造方法,OkHttpClient 是通过 Builder 模式来创建实例对象的。
Builder 模式常常用来复杂对象的构造,通过使用 Builder 模式可以减少构造器或方法调用传入的参数数量。这对有很多个配置参数进行初始化的对象来说尤其适用。
注意:OkHttp 官方文档建议使用单例模式来创建 OkHttpClient ,因为每一个OkHttpClient 都有自己单独的连接池和线程池,复用连接池和线程池能减少延迟、节省内存。
public OkHttpClient() {
this(new OkHttpClient.Builder());
}
OkHttpClient(OkHttpClient.Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
this.eventListenerFactory = builder.eventListenerFactory;
this.proxySelector = builder.proxySelector;
...
}
public static final class Builder {
Dispatcher dispatcher; //调度器
@Nullable
Proxy proxy; //代理类,默认有三种代理模式DIRECT(直连),HTTP(http代理),SOCKS(socks代理)
List<Protocol> protocols; //协议集合,协议类,用来表示使用的协议版本,比如`http/1.0,`http/1.1,`spdy/3.1,`h2等
List<ConnectionSpec> connectionSpecs; //连接规范,用于配置Socket连接层。对于HTTPS,还能配置安全传输层协议(TLS)版本和密码套件
final List<Interceptor> interceptors = new ArrayList(); //拦截器,可以监听、重写和重试请求等
final List<Interceptor> networkInterceptors = new ArrayList();
okhttp3.EventListener.Factory eventListenerFactory;
ProxySelector proxySelector; //代理选择类,默认不使用代理,即使用直连方式,当然,我们可以自定义配置,以指定URI使用某种代理,类似代理软件的PAC功能。
CookieJar cookieJar; //Cookie的保存获取
@Nullable
Cache cache; //缓存类,内部使用了DiskLruCache来进行管理缓存,匹配缓存的机制不仅仅是根据url,而且会根据请求方法和请求头来验证是否可以响应缓存。此外,仅支持GET请求的缓存。
@Nullable
InternalCache internalCache; //内置缓存
SocketFactory socketFactory; //Socket的抽象创建工厂,通过createSocket来创建Socket
@Nullable
SSLSocketFactory sslSocketFactory; //安全套接层工厂,HTTPS相关,用于创建SSLSocket。一般配置HTTPS证书信任问题都需要从这里着手。对于不受信任的证书一般会提示
javax.net.ssl.SSLHandshakeException异常。
@Nullable
CertificateChainCleaner certificateChainCleaner; //证书链清洁器,HTTPS相关,用于从[Java]的TLS API构建的原始数组中统计有效的证书链,然后清除跟TLS握手不相关的证书,提取可信任的证书以便可以受益于证书锁机制。
HostnameVerifier hostnameVerifier; //主机名验证器,与HTTPS中的SSL相关,当握手时如果URL的主机名不是可识别的主机,就会要求进行主机名验证
CertificatePinner certificatePinner; // 证书锁,HTTPS相关,用于约束哪些证书可以被信任,可以防止一些已知或未知的中间证书机构带来的攻击行为。如果所有证书都不被信任将抛出SSLPeerUnverifiedException异常。
Authenticator proxyAuthenticator; //身份认证器,当连接提示未授权时,可以通过重新设置请求头来响应一个新的Request。状态码401表示远程服务器请求授权,407表示代理服务器请求授权。该认证器在需要时会被RetryAndFollowUpInterceptor触发。
Authenticator authenticator;
ConnectionPool connectionPool; //连接池
Dns dns; //域名解析系统
boolean followSslRedirects; //是否遵循SSL重定向
boolean followRedirects; //是否重定向
boolean retryOnConnectionFailure; //失败是否重新连接
int connectTimeout; //连接超时
int readTimeout; //读取超时
int writeTimeout; //写入超时
int pingInterval; //与WebSocket有关,为了保持长连接,我们必须间隔一段时间发送一个ping指令进行保活;
public Builder() {
this.dispatcher = new Dispatcher();
this.protocols = OkHttpClient.DEFAULT_PROTOCOLS; //默认支持的协议
this.connectionSpecs = OkHttpClient.DEFAULT_CONNECTION_SPECS; //默认的连接规范
this.eventListenerFactory = EventListener.factory(EventListener.NONE);
this.proxySelector = ProxySelector.getDefault(); //默认的代理选择器,直连
this.cookieJar = CookieJar.NO_COOKIES; //默认不进行管理Cookie
this.socketFactory = SocketFactory.getDefault();
this.hostnameVerifier = OkHostnameVerifier.INSTANCE; //主机验证
this.certificatePinner = CertificatePinner.DEFAULT; //证书锁,默认不开启
this.proxyAuthenticator = Authenticator.NONE; //默认不进行授权
this.authenticator = Authenticator.NONE;
this.connectionPool = new ConnectionPool(); //连接池
this.dns = Dns.SYSTEM;
this.followSslRedirects = true;
this.followRedirects = true;
this.retryOnConnectionFailure = true;
this.connectTimeout = 10000;
this.readTimeout = 10000;
this.writeTimeout = 10000;
this.pingInterval = 0;
}
...
}
dispatcher:请求分发器。后面会详细解释
eventListenerFactory :Call的状态监听器,注意这个是okhttp新添加的功能
hostnameVerifier、 certificatePinner、 proxyAuthenticator、 authenticator:都是安全相关的设置
connectionPool:连接池,我们通常将一个客户端和服务端和连接抽象为一个 connection,而每一个 connection 都会被存放在 connectionPool 中,由它进行统一的管理,例如有一个相同的 http 请求产生时,connection 就可以得到复用
Request
Request 同样也是使用了 Builder 模式来配置 head、method 等等的参数
public final class Request {
...
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tags = Util.immutableMap(builder.tags);
}
...
public static class Builder {
HttpUrl url;
String method;
Headers.Builder headers;
RequestBody body;
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
Builder(Request request) {
this.url = request.url;
this.method = request.method;
this.body = request.body;
this.tags = request.tags.isEmpty()
? Collections.<Class<?>, Object>emptyMap()
: new LinkedHashMap<>(request.tags);
this.headers = request.headers.newBuilder();
}
...
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
}
}
Call
在创建了 OkHttpClient 和 Request 的实例对象之后,就可以通过调用 okHttpclient.newCall(request) 来创建一个 Call 对象了。
OkHttpClient 的 newCall 方法实际上是调用了 RealCall(Call的实现类)的 newRealCall 方法:
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false);
}
接着跟进来看一下 RealCall.newRealCall 方法的实现
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
newRealCall 方法创建了一个 RealCall 的实例,同时还赋值了一个Listener,然后就返回了
接着看一下 RealCall 的构造函数
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
可以看到在 RealCall 的构造方法里其实是持有了之前被初始化了的 OkHttpClient 和 Request 这两个对象,同时还赋值了一个 RetryAndFollowUpInterceptor 重定向拦截器。
到目前为止,Call 对象就被 okHttpClient 的 newCall 方法给创建完成了,接下来我们就走到了第四步,通过调用 call.execute() 来发送同步请求。
call#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 result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
这段代码的开头我们看到在一个同步代码块中,判断一个 标志位 executed 是否为 true,这表示说同一个 http 同步请求只能执行一次
接着来看一下 execute 方法最关键的地方:
client.dispacher().executed(this);
client.dispacher() 方法返回了一个 dispatcher 对象,然后接着调用了 dispacher 对象的 executed 方法来发送同步请求
Dispatcher#executed:
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
...
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
也就是说一个同步请求实际上只是调用了 executed() 方法来把一个同步任务添加到了 runningSyncCalls 队列之中。
在将任务添加到队列之后,就调用了 getResponseWithInterceptorChain() 方法来获取 Response,在方法内部会依次调用拦截器来进行相应的操作。
最后,在 finally 代码块中会调用 dispatcher 的 finished() 方法来将一个请求从请求队列中移除
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); //如果无法移除该请求则抛出异常
if (promoteCalls) promoteCalls(); //同步请求时为 false,不会触发该方法,但异步请求时触发
runningCallsCount = runningCallsCount(); //返回正在执行的异步请求和同步请求的总数量
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
至此,同步请求的流程就结束了,其实在同步请求中 dispatcher 所做的工作很简单,只有保存同步请求和移除同步请求。
1.2 异步请求流程源码分析
与前面同步请求不同,Okhttp 通过调用 Call 对象的 enqueue 来实现异步的网络请求
@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));
}
enqueue 方法的前部分与 同步请求的 executed 方法相同,都是使用一个标志位 executed 来判断当前的 RealCall 对象是否已被执行过,如果已被执行过,则会抛出异常。
最后会调用 dispatcher 的 enqueue 方法来处理异常请求,注意到 enqueue 方法传入了一个 封装了响应回调 callBack 的 AsyncCall 对象,AsyncCall 是 Runnable 的一个实现类。
2. Dispatcher
通过上面的分析,我们已经了解了OkHttp 同步请求和异步请求的实现流程,同时也知道了实际的同步/异步请求都是有 dispatcher 这个分发器类来完成的。
client.dispatcher().executed(this); //同步请求
client.dispatcher().enqueue(new AsyncCall(responseCallback)); //异步请求
OkHttpClient 的 dispatcher 方法仅仅是返回了一个 dispatcher 对象
public Dispatcher dispatcher() {
return dispatcher;
}
细心的朋友可能还记得,dispatcher 是在 OkHttpClient 的 Builder 方法中初始化的
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
...
之前我们已经知道了 dispatcher.executed 同步方法的作用就是把一个同步任务添加到 runningSyncCalls 队列之中,接着我们就可以来看一下 dispatcher.enqueue 异步方法具体是怎么实现的
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<>();
...
synchronized void enqueue(AsyncCall call) {
//判断 runningAsyncCalls 请求队列中的 AsyncCall(即Runnable) 的数量是否大于默认的最大并发请求数(默认为64)
//判断 正在运行的主机请求数是否大于默认的每个主机的最大请求数(默认为5)
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//当满足条件时,则把 异步请求Runable(AsyncCall)添加到 runningAsyncCalls 异步队列中
runningAsyncCalls.add(call);
//通过线程池来执行请求 call
executorService().execute(call);
} else {
//否则加入到等待队列中
readyAsyncCalls.add(call);
}
}
...
/** Returns the number of running calls that share a host with {@code call}. */
private int runningCallsForHost(AsyncCall call) {
int result = 0;
for (AsyncCall c : runningAsyncCalls) {
if (c.get().forWebSocket) continue;
if (c.host().equals(call.host())) result++;
}
return result;
}
}
在了解了 call 请求会被添加到请求队列里之后,我们再来分析一下线程池是如何执行请求的
可以看到在 executorService() 这个方法中创建了一个 ThreadPoolExecutor 的单例
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;
}
然后就调用了 executorService 线程池的 execute 方法,也就是说最终会调用线程池中每一个线程的 run 方法,也就是传入该方法的 AsyncCall 对象的 run() 方法,但是我们查看源码发现 AsyncCall 中并没有 run 方法,不过同时也发现 AsyncCall 继承自 NamedRunnable, NamedRunnable 的 run() 方法调用了 execute() 这个抽象方法,而 AsyncCall 实现了 execute() 这个抽象方法。
/**
* Runnable implementation which always sets its thread name.
*/
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
所以说 AsyncCall 的 execute 方法才是真正实现操作的地方
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@Override protected void execute() {
boolean signalledCallback = false;
try {
//通过拦截链获取 response 对象,并根据情况调用 callBack 的回调方法
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
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 {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
//finished 方法的作用在上面介绍同步请求的地方介绍过了
client.dispatcher().finished(this);
}
}
}
这里最后一步 finished 的作用在上面同步请求的地方也用到了,这里再详细介绍一下
/**
* @params calls 同步请求时传入 runningSyncCalls ,异步请求是传入的为 runningAsyncCalls
* promoteCalls 同步请求时传入false,异步时传入true
*/
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized(this) {
//将当前 call 请求从请求队列中删除
if (!calls.remove(call)) {
throw new AssertionError("Call wasn't in-flight!");
}
//遍历 runningAsyncCalls 接下来的 call,并通过 executorService 执行
if (promoteCalls) {
this.promoteCalls();
}
//重新计算正在执行的线程数量
runningCallsCount = this.runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
到此我们就经历了一次完整的同步/异步网络请求了。
参考:
https://blog.csdn.net/json_it/article/details/78404010
https://blog.csdn.net/lintcgirl/article/details/52213570
https://www.jianshu.com/p/e3b6f821acb8
okHttp3 源码分析的更多相关文章
- Android面试题-OkHttp3源码分析
本文配套视频: okhttp内核分析配套视频一 okhttp内核分析配套视频二 okhttp内核分析配套视频三 源码分析相关面试题 Volley源码分析 注解框架实现原理 基本使用 从使用方法出发,首 ...
- OkHttp3 拦截器源码分析
OkHttp 拦截器流程源码分析 在这篇博客 OkHttp3 拦截器(Interceptor) ,我们已经介绍了拦截器的作用,拦截器是 OkHttp 提供的对 Http 请求和响应进行统一处理的强大机 ...
- Okhttp3源码解析(3)-Call分析(整体流程)
### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...
- Okhttp3源码解析(2)-Request分析
### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...
- Retrofit源码分析(一)
1.基本用法 创建接口 public interface GitHubService { @GET("users/{user}/repos") Observable<List ...
- OkHttp3源码详解(三) 拦截器
1.构造Demo 首先构造一个简单的异步网络访问Demo: OkHttpClient client = new OkHttpClient(); Request request = new Reques ...
- 源码分析Retrofit请求流程
Retrofit 是 square 公司的另一款广泛流行的网络请求框架.前面的一篇文章<源码分析OKHttp执行过程>已经对 OkHttp 网络请求框架有一个大概的了解.今天同样地对 Re ...
- Okhttp3源码解析(4)-拦截器与设计模式
### 前言 回顾: [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析](htt ...
- Okhttp3源码解析(5)-拦截器RetryAndFollowUpInterceptor
### 前言 回顾: [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析](htt ...
随机推荐
- JDBC:JAVA & Oracle
JDBC:JAVA & Oracle 本文中未加标注的资源均来自于PolyU数据库课程的实验材料.仅作为学习使用,如有侵权,请联系删除 JDBC是什么 我之前写过一篇关于数据库和JAVA的博文 ...
- 5.Struts2-Struts标签
通用标签 1.property(取值) property:<s:property value="username"/> property 取值为字符串:<s:pr ...
- vue防重复点击(指令实现)
快速点击按钮会重复多次调用接口,防止出现这样的情况 全局定义,方便调用 新建plugins.js export default { install (Vue) { // 防重复点击(指令实现) Vue ...
- SokcetClient VC++6.0
// SokcetClient.cpp: implementation of the SokcetClient class. // ////////////////////////////////// ...
- JavaMaven【四、坐标&构件】
maven的依赖都是使用坐标找到对应的构件来进行的 坐标 即groupId+artifactId+version 上图第一个红框是本项目的坐标 第二个红框是依赖的项目的坐标 构件 坐标对应的jar包 ...
- 迭代器遍历【List、Set、Map】
迭代器遍历[List.Set.Map] example package boom.collection; import java.util.ArrayList; import java.util.Ha ...
- BLE 5协议栈-属性协议层(ATT)
文章转载自:http://www.sunyouqun.com/2017/04/page/2/ 属性协议(Attribute Protocol)简称ATT. ATT层定义了属性实体的概念,包括UUID. ...
- shell常用分隔符及管道的用法
1.命令1;命令2;命令3;.... 代码顺序执行 2.&&连接两条命令:命令1&&命令2&&命令3... 短路执行 3.||连接两条命令:命令1||命 ...
- CNN for NLP(2)
参考链接: 卷积神经网络(CNN)在句子建模上的应用, 卷积神经网络CNN在自然语言处理中的应用, CNN在NLP中的应用.
- 源码安装 qemu-2.0.0 及其依赖 glib-2.12.12
源码安装qemu-2.0.0 下载源代码并解压 http://wiki.qemu-project.org/download/qemu-2.0.0.tar.bz2 .tar.gz 编译及安装: cd q ...