一, 前言

在上一篇博客OkHttp3 使用详解里,我们已经介绍了 OkHttp 发送同步请求和异步请求的基本使用方法。

OkHttp 提交网络请求需要经过这样四个步骤:

  1. 初始化 OkHttpClient
  2. 创建 Request
  3. 创建 Call 对象(okHttpClient.newCall(request))
  4. 通过 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 源码分析的更多相关文章

  1. Android面试题-OkHttp3源码分析

    本文配套视频: okhttp内核分析配套视频一 okhttp内核分析配套视频二 okhttp内核分析配套视频三 源码分析相关面试题 Volley源码分析 注解框架实现原理 基本使用 从使用方法出发,首 ...

  2. OkHttp3 拦截器源码分析

    OkHttp 拦截器流程源码分析 在这篇博客 OkHttp3 拦截器(Interceptor) ,我们已经介绍了拦截器的作用,拦截器是 OkHttp 提供的对 Http 请求和响应进行统一处理的强大机 ...

  3. Okhttp3源码解析(3)-Call分析(整体流程)

    ### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...

  4. Okhttp3源码解析(2)-Request分析

    ### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...

  5. Retrofit源码分析(一)

    1.基本用法 创建接口 public interface GitHubService { @GET("users/{user}/repos") Observable<List ...

  6. OkHttp3源码详解(三) 拦截器

    1.构造Demo 首先构造一个简单的异步网络访问Demo: OkHttpClient client = new OkHttpClient(); Request request = new Reques ...

  7. 源码分析Retrofit请求流程

    Retrofit 是 square 公司的另一款广泛流行的网络请求框架.前面的一篇文章<源码分析OKHttp执行过程>已经对 OkHttp 网络请求框架有一个大概的了解.今天同样地对 Re ...

  8. Okhttp3源码解析(4)-拦截器与设计模式

    ### 前言 回顾: [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析](htt ...

  9. Okhttp3源码解析(5)-拦截器RetryAndFollowUpInterceptor

    ### 前言 回顾: [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析](htt ...

随机推荐

  1. JDBC:JAVA & Oracle

    JDBC:JAVA & Oracle 本文中未加标注的资源均来自于PolyU数据库课程的实验材料.仅作为学习使用,如有侵权,请联系删除 JDBC是什么 我之前写过一篇关于数据库和JAVA的博文 ...

  2. 5.Struts2-Struts标签

    通用标签 1.property(取值) property:<s:property value="username"/> property 取值为字符串:<s:pr ...

  3. vue防重复点击(指令实现)

    快速点击按钮会重复多次调用接口,防止出现这样的情况 全局定义,方便调用 新建plugins.js export default { install (Vue) { // 防重复点击(指令实现) Vue ...

  4. SokcetClient VC++6.0

    // SokcetClient.cpp: implementation of the SokcetClient class. // ////////////////////////////////// ...

  5. JavaMaven【四、坐标&构件】

    maven的依赖都是使用坐标找到对应的构件来进行的 坐标 即groupId+artifactId+version 上图第一个红框是本项目的坐标 第二个红框是依赖的项目的坐标 构件 坐标对应的jar包 ...

  6. 迭代器遍历【List、Set、Map】

    迭代器遍历[List.Set.Map] example package boom.collection; import java.util.ArrayList; import java.util.Ha ...

  7. BLE 5协议栈-属性协议层(ATT)

    文章转载自:http://www.sunyouqun.com/2017/04/page/2/ 属性协议(Attribute Protocol)简称ATT. ATT层定义了属性实体的概念,包括UUID. ...

  8. shell常用分隔符及管道的用法

    1.命令1;命令2;命令3;.... 代码顺序执行 2.&&连接两条命令:命令1&&命令2&&命令3... 短路执行 3.||连接两条命令:命令1||命 ...

  9. CNN for NLP(2)

    参考链接: 卷积神经网络(CNN)在句子建模上的应用, 卷积神经网络CNN在自然语言处理中的应用, CNN在NLP中的应用.

  10. 源码安装 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 ...