在上一节介绍了缓存拦截器 CacheInterceptor 的缓存机制, 内部采用 DiskLruCache 来缓存数据, 本节介绍剩下的两个拦截器, 分别是 ConnectInterceptor 和 CallServerInterceptor 拦截器.
ConnectInterceptor 拦截器
ConnectInterceptor 是网络连接拦截器, 我们知道在 OkHttp 当中真正的网络请求都是通过拦截器链来实现的, 通过依次执行这个拦截器链上不同功能的拦截器来完成数据的响应, ConnectInterceptor 的作用就是打开与服务器之间的连接, 正式开启 OkHttp 的网络请求.
走进 ConnectInterceptor 的 intercept 方法:
- @Override public Response intercept(Interceptor.Chain chain) throws IOException {
- RealInterceptorChain realChain = (RealInterceptorChain) chain;
- Request request = realChain.request();
- // 标记 1
- StreamAllocation streamAllocation = realChain.streamAllocation();
- ...
- }
在标记 1 处可以看到从上一个拦截器中获取 StreamAllocation 对象, 在讲解第一个拦截器 RetryAndFollowUpInterceptor 重试重定向的时候已经介绍过 StreamAllocation, 在 RetryAndFollowUpInterceptor 中只是创建了这个对象并没有使用, 真正使用它的是在 ConnectInterceptor 中, StreamAllocation 是用来建立执行 HTTP 请求所需要的网络组件, 既然我们拿到了 StreamAllocation, 接下来看这个 StreamAllocation 到底做了哪些操作.
- @Override public Response intercept(Interceptor.Chain chain) throws IOException {
- ...
- // 标记 1
- StreamAllocation streamAllocation = realChain.streamAllocation();
- ..
- // 标记 2
- HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
- ...
- }
在标记 2 处通过 StreamAllocation 对象的 newStream 方法创建了一个 HttpCodec 对象, HttpCodec 的作用是用来编码我们的 Request 以及解码我们的 Response.
- @Override public Response intercept(Interceptor.Chain chain) throws IOException {
- ...
- // 标记 1
- StreamAllocation streamAllocation = realChain.streamAllocation();
- ...
- // 标记 2
- HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
- // 标记 3
- RealConnection connection = streamAllocation.connection();
- ...
- }
在标记 3 处通过 StreamAllocation 对象的 connection 方法获取到 RealConnection 对象, 这个 RealConnection 对象是用来进行实际的网络 IO 传输的.
- @Override public Response intercept(Interceptor.Chain chain) throws IOException {
- ...
- // 标记 1
- StreamAllocation streamAllocation = realChain.streamAllocation();
- ...
- // 标记 2
- HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
- // 标记 3
- RealConnection connection = streamAllocation.connection();
- // 标记 4
- return realChain.proceed(request, streamAllocation, httpCodec, connection);
- }
标记 4 处是我们非常熟悉的代码了, 继续调用拦截器链的下一个拦截器并将 Request,StreamAllocation,HttpCodec 以及 RealConnection 对象传递过去.
总结: 首先 ConnectInterceptor 拦截器从拦截器链获取到前面传递过来的 StreamAllocation, 接着执行 StreamAllocation 的 newStream 方法创建 HttpCodec,HttpCodec 对象是用于处理我们的 Request 和 Response. 最后将刚才创建的用于网络 IO 的 RealConnection 对象, 以及对于服务器交互最为关键的 HttpCodec 等对象传递给后面的拦截器.
从上面我们了解了 ConnectInterceptor 拦截器的 intercept 方法的整体流程, 从前一个拦截器中获取 StreamAllocation 对象, 通过 StreamAllocation 对象的 newStream 方法创建了一个 HttpCodec 对象, 我们看看这个 newStream 方法具体做了哪些操作.
- public HttpCodec newStream(
- OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
- ...
- try {
- // 标记 1
- RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
- writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
- HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
- ...
- } catch (IOException e) {
- throw new RouteException(e);
- }
- }
我们可以看到在标记 1 处创建了一个 RealConnection 对象, 以及 HttpCodec 对象, 这两个对象在上面已经介绍过了, RealConnection 对象是用来进行实际的网络 IO 传输的, HttpCodec 是用来编码我们的 Request 以及解码我们的 Response.
通过 findHealthyConnection 方法生成一个 RealConnection 对象, 来进行实际的网络连接.
findHealthyConnection 方法:
- private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
- int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
- boolean doExtensiveHealthChecks) throws IOException {
- while (true) {
- RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
- pingIntervalMillis, connectionRetryEnabled);
- ...
- synchronized (connectionPool) {
- if (candidate.successCount == 0) {
- return candidate;
- }
- }
- ...
- return candidate;
- }
- }
在方法中开启了 while 循环, 内部的同步代码块中判断 candidate 的 successCount 如果等于 0, 说明整个网络连接结束并直接返回 candidate, 而这个 candidate 是通过同步代码块上面的 findConnection 方法获取的.
- private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
- int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
- boolean doExtensiveHealthChecks) throws IOException {
- while (true) {
- RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
- pingIntervalMillis, connectionRetryEnabled);
- synchronized (connectionPool) {
- if (candidate.successCount == 0) {
- return candidate;
- }
- }
- // 标记 1
- if (!candidate.isHealthy(doExtensiveHealthChecks)) {
- noNewStreams();
- continue;
- }
- return candidate;
- }
- }
往下看标记 1, 这边会判断这个连接是否健康 (比如 Socket 没有关闭, 或者它的输入输出流没有关闭等等), 如果不健康就调用 noNewStreams 方法从连接池中取出并销毁, 接着调用 continue, 继续循环调用 findConnection 方法获取 RealConnection 对象.
通过不停的循环调用 findConnection 方法来获取 RealConnection 对象, 接着看这个 findConnection 方法做了哪些操作.
- private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
- int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
- ...
- RealConnection result = null;
- Connection releasedConnection;
- ...
- synchronized (connectionPool) {
- ...
- // 标记 1
- releasedConnection = this.connection;
- ...
- if (this.connection != null) {
- result = this.connection;
- releasedConnection = null;
- }
- ...
- }
- ...
- return result;
- }
在 findConnection 方法的标记 1 处, 尝试将 connection 赋值给 releasedConnection, 然后判断这个 connection 能不能复用, 如果能复用, 就将 connection 赋值给 result, 最后返回这个复用的连接. 如果不能复用, 那么 result 就为 null, 我们继续往下看.
- private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
- int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
- ...
- RealConnection result = null;
- Connection releasedConnection;
- ...
- synchronized (connectionPool) {
- ...
- // 标记 1
- ...
- // 标记 2
- if (result == null) {
- Internal.instance.get(connectionPool, address, this, null);
- if (connection != null) {
- foundPooledConnection = true;
- result = connection;
- } else {
- selectedRoute = route;
- }
- }
- ...
- }
- ...
- return result;
- }
如果 result 为 null 说明不能复用这个 connection, 那么就从连接池 connectionPool 中获取一个实际的 RealConnection 并赋值给 connection, 接着判断 connection 是否为空, 不为空赋值给 result.
- private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
- int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
- ...
- RealConnection result = null;
- Connection releasedConnection;
- ...
- synchronized (connectionPool) {
- ...
- // 标记 1
- ...
- // 标记 2
- if (result == null) {
- Internal.instance.get(connectionPool, address, this, null);
- if (connection != null) {
- foundPooledConnection = true;
- result = connection;
- } else {
- selectedRoute = route;
- }
- }
- ...
- }
- ...
- // 标记 3
- result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
- connectionRetryEnabled, call, eventListener);
- ...
- return result;
- }
标记 3 处, 拿到我们的 RealConnection 对象 result 之后, 调用它的 connect 方法来进行实际的网络连接.
- private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
- int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
- ...
- RealConnection result = null;
- Connection releasedConnection;
- ...
- synchronized (connectionPool) {
- ...
- // 标记 1
- ...
- // 标记 2
- if (result == null) {
- Internal.instance.get(connectionPool, address, this, null);
- if (connection != null) {
- foundPooledConnection = true;
- result = connection;
- } else {
- selectedRoute = route;
- }
- }
- ...
- }
- ...
- // 标记 3
- result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
- connectionRetryEnabled, call, eventListener);
- ...
- // 标记 4
- Internal.instance.put(connectionPool, result);
- ...
- return result;
- }
在标记 4 处, 进行真正的网络连接后, 将连接成功后的 RealConnection 对象 result 放入 connectionPool 连接池, 方便后面复用.
上面我们介绍了 StreamAllocation 对象的 newStream 方法的具体操作, 接下来看看 ConnectInterceptor 拦截器中一个很重要的概念 - 连接池.
不管 HTTP 协议是 1.1 还是 2.0, 它们的 Keep-Alive 机制, 或者 2.0 的多路复用机制在实现上都需要引入一个连接池的概念, 来维护整个网络连接. OkHttp 中将客户端与服务端之间的链接抽象成一个 Connection 类, 而 RealConnection 是它的实现类, 为了管理所有的 Connection,OkHttp 提供了一个 ConnectionPool 这个类, 它的主要作用就是在时间范围内复用 Connection.
接下来主要介绍它的 get 和 put 方法.
- @Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
- assert (Thread.holdsLock(this));
- for (RealConnection connection : connections) {
- if (connection.isEligible(address, route)) {
- streamAllocation.acquire(connection, true);
- return connection;
- }
- }
- return null;
- }
在 get 方法中遍历连接池中的 Connection, 通过 isEligible 方法判断 Connection 是否可用, 如果可以使用就会调用 streamAllocation 的 acquire 方法来获取所用的连接.
进入 StreamAllocation 的 acquire 方法:
- public void acquire(RealConnection connection, boolean reportedAcquired) {
- assert (Thread.holdsLock(connectionPool));
- if (this.connection != null) throw new IllegalStateException();
- // 标记 1
- this.connection = connection;
- this.reportedAcquired = reportedAcquired;
- // 标记 2
- connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
- }
标记 1 处, 从连接池中获取的 RealConnection 对象赋值给 StreamAllocation 的成员变量 connection.
标记 2 处, 将 StreamAllocation 对象的弱引用添加到 RealConnection 的 allocations 集合中去, 这样做的用处是通过 allocations 集合的大小来判断网络连接次数是否超过 OkHttp 指定的连接次数.
put 方法:
- void put(RealConnection connection) {
- assert (Thread.holdsLock(this));
- if (!cleanupRunning) {
- cleanupRunning = true;
- executor.execute(cleanupRunnable);
- }
- connections.add(connection);
- }
put 方法中在添加连接到连接池之前, 会处理清理任务, 做完清理任务后, 将我们的 connection 添加到连接池中.
connection 自动回收 l 利用了 GC 的回收算法, 当 StreamAllocation 数量为 0 时, 会被线程池检测到, 然后进行回收, 在 ConnectionPool 中有一个独立的线程, 它会开启 cleanupRunnable 来清理连接池.
- private final Runnable cleanupRunnable = new Runnable() {
- @Override public void run() {
- while (true) {
- // 标记 1
- long waitNanos = cleanup(System.nanoTime());
- if (waitNanos == -1) return;
- if (waitNanos> 0) {
- long waitMillis = waitNanos / 1000000L;
- waitNanos -= (waitMillis * 1000000L);
- synchronized (ConnectionPool.this) {
- try {
- // 标记 2
- ConnectionPool.this.wait(waitMillis, (int) waitNanos);
- } catch (InterruptedException ignored) {
- }
- }
- }
- }
- }
- };
在 run 方法中是一个死循环, 内部标记 1 处首次进行清理时, 需要返回下次清理的间隔时间. 标记 2 处调用了 wait 方法进行等待, 等待释放锁和时间片, 当等待时间过了之后会再次调用 Runnable 进行清理, 同时返回下次要清理的间隔时间 waitNanos.
标记 2 处的 cleanup 方法内部实现了具体的 GC 回收算法, 该算法类似 Java GC 当中的标记清除算法; cleanup 方法循环标记出最不活跃的 connection, 通过响应的判断来进行清理.
CallServerInterceptor 拦截器
CallServerInterceptor 拦截器主要作用是负责向服务器发起真正的网络请求, 并获取返回结果.
CallServerInterceptor 的 intercept 方法:
- @Override public Response intercept(Chain chain) throws IOException {
- RealInterceptorChain realChain = (RealInterceptorChain) chain;
- HttpCodec httpCodec = realChain.httpStream();
- StreamAllocation streamAllocation = realChain.streamAllocation();
- RealConnection connection = (RealConnection) realChain.connection();
- Request request = realChain.request();
- ...
- }
intercept 方法中先是获取五个对象, 下面分别介绍这 5 个对象的含义.
RealInterceptorChain: 拦截器链, 真正进行请求的地方.
HttpCodec: 在 OkHttp 中, 它把所有的流对象都封装成了 HttpCodec 这个类, 作用是编码 Request, 解码 Response.
StreamAllocation: 建立 HTTP 连接所需要的网络组件.
RealConnection: 服务器与客户端的具体连接.
Request: 网络请求.
- @Override public Response intercept(Chain chain) throws IOException {
- RealInterceptorChain realChain = (RealInterceptorChain) chain;
- HttpCodec httpCodec = realChain.httpStream();
- StreamAllocation streamAllocation = realChain.streamAllocation();
- RealConnection connection = (RealConnection) realChain.connection();
- Request request = realChain.request();
- ...
- // 标记 1
- httpCodec.finishRequest();
- ...
- }
标记 1 处, 调用 httpCodec 的 finishRequest 方法, 表面网络请求的写入工作已经完成, 具体网络请求的写入工作大家可以看源码, 也就是标记 1 之上的代码.
网络请求的写入工作完成后, 接下来就进行网络请求的读取工作.
- @Override public Response intercept(Chain chain) throws IOException {
- RealInterceptorChain realChain = (RealInterceptorChain) chain;
- HttpCodec httpCodec = realChain.httpStream();
- StreamAllocation streamAllocation = realChain.streamAllocation();
- RealConnection connection = (RealConnection) realChain.connection();
- Request request = realChain.request();
- ...
- // 网络请求一系列写入工作
- ...
- // 向 socket 当中写入请求的 body 信息
- request.body().writeTo(bufferedRequestBody);
- ...
- // 标记 1: 写入结束
- httpCodec.finishRequest();
- ...
- if (responseBuilder == null) {
- realChain.eventListener().responseHeadersStart(realChain.call());
- // 读取网络写入的头部信息
- responseBuilder = httpCodec.readResponseHeaders(false);
- }
- Response response = responseBuilder
- .request(request)
- .handshake(streamAllocation.connection().handshake())
- .sentRequestAtMillis(sentRequestMillis)
- .receivedResponseAtMillis(System.currentTimeMillis())
- .build();
- ...
- // 读取 Response
- // 标记 1
- response = response.newBuilder()
- .body(httpCodec.openResponseBody(response))
- .build();
- ...
- return response;
- }
我们只取核心代码标记 1, 通过 httpCodec 的 openResponseBody 方法获取 body, 并通过 build 创建 Response 对象, 最终返回 Response 对象.
到这里 OkHttp 的同步和异步请求, 分发器, 以及五个拦截器都已经介绍一边了, 怎么说呢, OkHttp 的源码实在太庞大了, 要想全部理解需要花费很长时间, 我只是整理出了 OkHttp 中几个比较重要的概念, 了解它的整体脉络, 这样你才能有条理的分析它的源码.
来源: https://juejin.im/post/5bd500316fb9a05d344805c3