okhttp3源码分析之拦截器

前言

本篇文章继续经过源码来探讨okhttp的另一个重要知识点:拦截器,在上一篇文章咱们知道,在请求发送到服务器以前有一系列的拦截器对请求作了处理后才发送出去,在服务器返回响应以后,一样的有一系列拦截器对响应作了处理后才返回给发起请求的调用者,可见,拦截器是okhttp的一个重要的核心功能,在分析各个拦截器功能的同时又会牵扯出okhttp的缓存机制、链接机制。java

本文源码基于okhttp3.14.xgit

okhttp项目地址:okhttpgithub

拦截器的简单使用

自定义一个拦截器须要实现Interceptor接口,接口定义以下:缓存

public interface Interceptor {
   
  //咱们须要实现这个intercept(chain)方法,在里面定义咱们的拦截逻辑
  Response intercept(Chain chain) throws IOException;

  interface Chain {
      
     //返回Request对象
    Request request();

    //调用Chain的proceed(Request)方法处理请求,最终返回Response
    Response proceed(Request request) throws IOException;


    //若是当前是网络拦截器,该方法返回Request执行后创建的链接
    //若是当前是应用拦截器,该方法返回null
    @Nullable Connection connection();

    //返回对应的Call对象
    Call call();

     //下面的方法见名知意,返回或写入超时
    int connectTimeoutMillis();
    Chain withConnectTimeout(int timeout, TimeUnit unit);
    int readTimeoutMillis();
    Chain withReadTimeout(int timeout, TimeUnit unit);
    int writeTimeoutMillis();
    Chain withWriteTimeout(int timeout, TimeUnit unit) } } 复制代码

能够看到Interceptor由两部分组成:intercept(Chain)方法和内部接口Chain,下面是自定义一个拦截器的通用逻辑,以下:服务器

public class MyInterceptor implements Interceptor {   
    @Override
    public Response intercept(Chain chain) throws IOException {
        
        //一、经过传进来的Chain获取Request
        Request request = chain.request();
        
      	//二、 处理Request,逻辑本身写
        //...
        
        //三、调用Chain的proceed(Request)方法处理请求,获得Response
        Response response = chain.proceed(request);
     
        //四、 处理Response,逻辑本身写
        //...
        
        //五、返回Response
        return response;
    }
}
复制代码

上述就是一个拦截器的通用逻辑,首先咱们继承Interceptor实现intercept(Chain)方法,完成咱们本身的拦截逻辑,即根据须要进行一、二、三、四、5步,不论是自定义拦截器仍是后面介绍的okhttp默认的拦截器大概都是这个模板实现,定义完拦截器后,咱们在构造OkhttpChient时就能够经过addInterceptor(Interceptor)或addNetworkInterceptor(Interceptor)添加自定义拦截器,以下:cookie

OkHttpClient client = new OkHttpClient.Builder()
     .addInterceptor(new MyInterceptor())
     .build();

或

OkHttpClient client = new OkHttpClient.Builder()
     .addNetworkInterceptor(new MyInterceptor())
     .build();
复制代码

这样okhttp在链式调用拦截器处理请求时就会调用到咱们自定义的拦截器,那么addInterceptor(Interceptor)和addNetworkInterceptor(Interceptor)有什么不同呢?它们一个是添加应用拦截器,一个是添加网络拦截器,主要是调用的时机不同,更多区别能够参考官方WIKI文档Okhttp-wiki 之 Interceptors 拦截器,当咱们平时作应用开发使用addInterceptor(Interceptor)就好了。网络

上述是咱们自定义的拦截器,下面咱们来看看okhttp默认的拦截器都干了什么。并发

RealCall :: getResponseWithInterceptorChain()

在上一篇文章知道RealCall的getResponseWithInterceptorChain()是处理、发送请求而且返回响应的地方,咱们再看一遍getResponseWithInterceptorChain()方法的源码,以下:异步

//RealCall.java
Response getResponseWithInterceptorChain() throws IOException {
    //新建一个List用来保存拦截器
    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协议,WebSocket是什么?自行百度
      //添加咱们自定义的网络拦截器
      interceptors.addAll(client.networkInterceptors());
    }
    //添加负责发起请求获取响应的拦截器
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //构造第一个Chain
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
       //调用Chain的proceed(Request)方法处理请求
      Response response = chain.proceed(originalRequest);
      //...
      //返回响应
      return response;
    }
    //...省略异常处理
  }
复制代码

getResponseWithInterceptorChain()干了三件事:一、添加拦截器到interceptors列表中;二、构造第一个Chain;三、调用Chain的proceed(Request)方法处理请求。下面分别介绍:socket

一、添加拦截器到interceptors列表中

除了添加咱们自定义的拦截器外,还添加了默认的拦截器,以下:

  • 一、RetryAndFollowUpInterceptor:负责失败重试和重定向。
  • 二、BridgeInterceptor:负责把用户构造的Request转换为发送给服务器的Request和把服务器返回的Response转换为对用户友好的Response。
  • 三、CacheInterceptor:负责读取缓存以及更新缓存。
  • 四、ConnectInterceptor:负责与服务器创建链接并管理链接。
  • 五、CallServerInterceptor:负责向服务器发送请求和从服务器读取响应。

这几个默认的拦截器是本文的重点,在后面会分别介绍。

二、构造第一个Chain

Chain是Interceptor的一个内部接口,它的实现类是RealInterceptorChain,咱们要对它的传进来的前6个构造参数有个印象,以下:

public final class RealInterceptorChain implements Interceptor.Chain {
    
    //...
    
    public RealInterceptorChain(List<Interceptor> interceptors, Transmitter transmitter, @Nullable Exchange exchange, int index, Request request, Call call, int connectTimeout, int readTimeout, int writeTimeout) {
        this.interceptors = interceptors;//interceptors列表
        this.transmitter = transmitter;//Transmitter对象,后面会介绍
        this.exchange = exchange;//Exchange对象,后面会介绍
        this.index = index;//interceptor索性,用于获取interceptors列表中的interceptor
        this.request = request;//请求request
        this.call = call;//Call对象
        //...
    }
    
    //...
}
复制代码

在后面的拦截器中均可以经过Chain获取这些传进来的参数。咱们知道,为了让每一个拦截器都有机会处理请求,okhttp使用了责任链模式来把各个拦截器串联起来,拦截器就是责任链的节点,而Chain就是责任链中各个节点之间的链接点,负责把各个拦截器链接起来。那么是怎么链接的?看下面的Chain的proceed方法。

三、调用Chain的proceed(Request)方法处理请求

实际是RealInterceptorChain的proceed(Request)方法,以下:

public final class RealInterceptorChain implements Interceptor.Chain {
    
    //...
    
    @Override 
    public Response proceed(Request request) throws IOException {
        return proceed(request, transmitter, exchange);
    }

    public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange) throws IOException {

        //index不能越界
        if (index >= interceptors.size()) throw new AssertionError();

        //...

        //再新建一个Chain,这里注意index加1,
        RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
                                                             index + 1, request, call, connectTimeout, readTimeout, writeTimeout);

        //获取interceptors列表中的下一个拦截器
        Interceptor interceptor = interceptors.get(index);

        //调用下一个拦截器的intercept(Chain)方法,传入刚才新建的RealInterceptorChain,返回Response
        Response response = interceptor.intercept(next);

        //...

        //返回响应
        return response;
    }
}
复制代码

proceed方法里面首先会再新建一个Chain而且index + 1做为构造参数传了进去,而后经过index从interceptors列表中获取了一个拦截器,接着就会调用拦截器的intercept方法,并把刚刚新建的Chain做为参数传给拦截器,咱们再回顾一下上面所讲的拦截器intercept方法的模板,intercept方法处理完Request逻辑后,会再次调用传入的Chain的proceed(Request)方法,这样又会重复Chain的proceed方法中的逻辑,因为index已经加1了,因此此次Chain就会经过index获取下一个拦截器,并调用下一个拦截器的intercept(Chain)方法,而后如此循环重复下去,这样就把每一个拦截器经过一个个Chain链接起来,造成一条链,把Request沿着链传递下去,直到请求被处理,而后返回Response,响应一样的沿着链传递上去,以下:

从上图可知,当没有自定义拦截器时,责任链首节点就是RetryAndFollowUpInterceptor,尾节点就是CallServerInterceptor,Request按照拦截器的顺序正向处理,Response则逆向处理,每一个拦截器都有机会处理Request和Response,一个完美的责任链模式的实现。

知道了getResponseWithInterceptorChain()的总体流程后,下面分别介绍各个默认拦截器的功能。

RetryAndFollowUpInterceptor

在自定义拦截器的时候就讲过,Interceptor的intercept(Chain)方法就是拦截器的拦截实现,RetryAndFollowUpInterceptor的intercept(Chain)方法以下:

//RetryAndFollowUpInterceptor.java
@Override 
public Response intercept(Chain chain) throws IOException {

    //获取Request
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    //获取Transmitter
    Transmitter transmitter = realChain.transmitter();

    //重定向次数
    int followUpCount = 0;
    Response priorResponse = null;
    
    //一个死循环
    while (true) {
        
        //调用Transmitter的prepareToConnect方法,作好链接创建的准备
        transmitter.prepareToConnect(request);

        if (transmitter.isCanceled()) {
            throw new IOException("Canceled");
        }
        
        Response response;
        boolean success = false;
        try {
            //调用proceed方法,里面调用下一个拦截器BridgeInterceptor的intercept方法
            response = realChain.proceed(request, transmitter, null);
            success = true;
        }catch (RouteException e) {//出现RouteException异常
            //调用recover方法检测链接是否能够继续使用
            if (!recover(e.getLastConnectException(), transmitter, false, request)) {
                throw e.getFirstConnectException();
            }
            continue;
        } catch (IOException e) {//出现IOException异常,和服务端创建链接失败
            boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
            //调用recover方法检测链接是否能够继续使用
            if (!recover(e, transmitter, requestSendStarted, request)) throw e;
            continue;
        } finally {//出现其余未知异常
            if (!success) {
                //调用Transmitter的exchangeDoneDueToException()方法释放链接
                transmitter.exchangeDoneDueToException();
            }
        }
        
        //执行到这里,没有出现任何异常,链接成功, 响应返回

        //...

        //根据响应码来处理请求头
        Request followUp = followUpRequest(response, route);

        //followUp为空,不须要重定向,直接返回Response
        if (followUp == null) {
            //...
            return response;
        }

        //followUp不为空,须要重定向

        //...

        //MAX_FOLLOW_UPS值为20,重定向次数不能大于20次
        if (++followUpCount > MAX_FOLLOW_UPS) {
            throw new ProtocolException("Too many follow-up requests: " + followUpCount);
        }

        //以重定向后的Request再次重试
        request = followUp;
        priorResponse = response;
    }
}
复制代码

RetryAndFollowUpInterceptor的intercept(Chain)方法中主要是失败重试和重定向的逻辑,该方法流程以下:

一、首先获取Transmitter类;

二、而后进入一个死循环,先调用Transmitter的prepareToConnect方法,准备创建链接;(链接真正的创建在ConnectInterceptor中)

三、接着调用Chain的proceed方法,继续执行下一个拦截器BridgeInterceptor的intercept方法:

​ 3.一、若是在请求的过程当中抛出RouteException异常或IOException异常,就会调用recover方法检测链接是否能够继续使用,若是不能够继续使用就抛出异常,整个过程结束,不然就再次重试,这就是失败重试;

​ 3.二、若是在请求的过程当中抛出除了3.1以外的异常,就会调用Transmitter的exchangeDoneDueToException()方法释放链接,整个过程结束。

四、没有任何异常抛出,当响应Response返回后,就会调用followUpRequest方法,里面根据返回的Response的响应码来决定是否须要重定向(构造followUp请求),若是不须要重定向,就直接返回Response,若是须要重定向,那么以重定向后的Request再次重试,重定向次数不能大于20次。

一、Transmitter

在整个方法的流程中出现了一个Transmitter,这里介绍一下,它是okhttp中应用层和网络层的桥梁,管理同一个Cal的全部链接、请求、响应和IO流之间的关系,它在RealCall建立后就被建立了,以下:

//RealCall.java
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    //建立RealCall
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    //建立Transmitter,赋值给call的transmitter字段
    call.transmitter = new Transmitter(client, call);
    return call;
}
复制代码

建立后,在构造节点Chain时做为参数传了进去,在getResponseWithInterceptorChain方法中有讲到,因此在intercept方法中它能够经过chain.transmitter()得到,它的整个生命周期贯穿了全部拦截器,在接下来的ConnectInterceptor和CallServerInterceptor中你均可以见到它的身影,咱们看一下它的主要成员,以下:

public final class Transmitter {
    
    private final OkHttpClient client;//OkHttpClient大管家
    private final RealConnectionPool connectionPool;//链接池,管理着链接
    public RealConnection connection;//本次链接对象
    private ExchangeFinder exchangeFinder;//负责链接的建立
    private @Nullable Exchange exchange;//负责链接IO流读写
    private final Call call;//Call对象
    
    //...
    
    public Transmitter(OkHttpClient client, Call call) {
        this.client = client;
        this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool());
        this.call = call;
        this.eventListener = client.eventListenerFactory().create(call);
        this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
    }

    
    public void prepareToConnect(Request request) {
        if (this.request != null) {
            if (sameConnection(this.request.url(), request.url()) && exchangeFinder.hasRouteToTry()) {
                return; // Already ready.
            }
           //...
        }
        this.request = request;
        //建立ExchangeFinder
        this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()), call, eventListener);
    }
    
}
复制代码

在Transmitter中client和call咱们都认识,剩下的RealConnectionPool、RealConnection、ExchangeFinder、Exchange都和okhttp的链接机制有关,都会在ConnectInterceptor中介绍,Transmitter就是负责管理它们之间的关系。这里咱们只要记住,Transmitter的prepareToConnect方法中主要是建立了一个ExchangeFinder,为在ConnectInterceptor中链接的创建作了一个准备。

BridgeInterceptor

BridgeInterceptor的intercept(Chain)方法以下:

//BridgeInterceptor.java
@Override 
public Response intercept(Chain chain) throws IOException {
    
    //获取Request
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();
  
    //下面都是根据须要为Request的header添加或移除一些信息
    
    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }

    //调用proceed方法,里面调用下一个拦截器CacheInterceptor的intercept方法
    Response networkResponse = chain.proceed(requestBuilder.build());

    //返回Response后
    //下面都是根据须要为Response的header添加或移除一些信息
    
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }
复制代码

BridgeInterceptor中的逻辑是在全部默认拦截器中是最简单,它主要就是对Request或Response的header作了一些处理,把用户构造的Request转换为发送给服务器的Request,还有把服务器返回的Response转换为对用户友好的Response。例如,对于Request,当开发者没有添加Accept-Encoding时,它会自动添加Accept-Encoding : gzip,表示客户端支持使用gzip;对于Response,当Content-Encoding是gzip方式而且客户端是自动添加gzip支持时,它会移除Content-Encoding、Content-Length,而后从新解压缩响应的内容。

CacheInterceptor

CacheInterceptor的intercept(Chain)方法以下:

//CacheInterceptor.java
@Override
public Response intercept(Chain chain) throws IOException {
    
    //根据Request获得Cache中缓存的Response,Cache是什么,后面介绍
    Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;

    long now = System.currentTimeMillis();

    //建立缓存策略:网络、缓存、或二者都使用,CacheStrategy是什么,后面介绍
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    //获得networkRequest
    Request networkRequest = strategy.networkRequest;
    //获得cacheResponse,cacheResponse等于上面的cacheCandidate
    Response cacheResponse = strategy.cacheResponse;

    //...

    //这个Response缓存无效,close掉它
    if (cacheCandidate != null && cacheResponse == null) {
        closeQuietly(cacheCandidate.body()); 
    }

    //一、networkRequest为null且cacheResponse为null:表示强制使用缓存,可是没有缓存,因此构造状态码为504,body为空的Response
    if (networkRequest == null && cacheResponse == null) {
        return new Response.Builder()
            .request(chain.request())
            .protocol(Protocol.HTTP_1_1)
            .code(504)//状态码504
            .message("Unsatisfiable Request (only-if-cached)")
            .body(Util.EMPTY_RESPONSE)
            .sentRequestAtMillis(-1L)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();
    }

   //二、networkRequest为null但cacheResponse不为null:表示强制使用缓存,而且有缓存,因此直接返回缓存的Response
    if (networkRequest == null) {
        return cacheResponse.newBuilder()
            .cacheResponse(stripBody(cacheResponse))
            .build();
    }

    Response networkResponse = null;
    try {
        //networkRequest不为null,因此能够发起网络请求,调用chain.proceed(Request),里面调用下一个拦截器BridgeInterceptor的intercept方法,会返回网络请求获得的networkResponse
        networkResponse = chain.proceed(networkRequest);
       
    } finally {
        //发起网络请求出现IO异常或其余异常的处理
        //...
    }

    //三、networkRequest不为null且cacheResponse不为null:由于cacheResponse不为null,因此根据网络请求获得的networkResponse和缓存的cacheResponse作比较,来决定是否更新cacheResponse
    if (cacheResponse != null) {
        if (networkResponse.code() == HTTP_NOT_MODIFIED) {//HTTP_NOT_MODIFIED等于304,304表示服务器缓存有更新,因此客户端要更新cacheResponse
            //下面根据networkResponse更新(从新构造)cacheResponse
            Response response = cacheResponse.newBuilder()
                .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                .cacheResponse(stripBody(cacheResponse))
                .networkResponse(stripBody(networkResponse))
                .build();
            networkResponse.body().close();
            //更新cacheResponse在本地缓存
            cache.trackConditionalCacheHit();
            cache.update(cacheResponse, response);
            return response;
        } else {//不须要更新cacheResponse,close掉它
            closeQuietly(cacheResponse.body());
        }
    }

    //四、networkRequest不为null但cacheResponse为null:cacheResponse为null,没有缓存使用,因此从networkResponse读取网络响应,构造Response准备返回
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    //把Response缓存到Cache中
    if (cache != null) {
        if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
            //cacheResponse为null,因此这里是第一次缓存,把Response缓存到Cache中
            CacheRequest cacheRequest = cache.put(response);
            return cacheWritingResponse(cacheRequest, response);
        }

        if (HttpMethod.invalidatesCache(networkRequest.method())) {
            try {
                cache.remove(networkRequest);
            } catch (IOException ignored) {
                // The cache cannot be written.
            }
        }
    }

    //返回Response
    return response;
}
复制代码

CacheInterceptor的intercept(Chain)里面定义了okhttp的缓存机制,咱们先来了解两个类:Cache和CacheStrategy,这样才能看懂intercept(Chain)里面的逻辑。

一、Cache - 缓存实现

Cache是okhttp中缓存的实现,内部使用了DiskLruCache,以下:

public final class Cache implements Closeable, Flushable {
    //...
    //内部都是经过DiskLruCache实现
    final DiskLruCache cache;
    
    //有一个InternalCache实现,都调用了Cache中的方法
    final InternalCache internalCache = new InternalCache() {

        @Override public @Nullable Response get(Request request) throws IOException {
            return Cache.this.get(request);
        }

        @Override public @Nullable CacheRequest put(Response response) throws IOException {
            return Cache.this.put(response);
        }

        @Override public void remove(Request request) throws IOException {
            Cache.this.remove(request);
        }

        @Override public void update(Response cached, Response network) {
            Cache.this.update(cached, network);
        }

        @Override public void trackConditionalCacheHit() {
            Cache.this.trackConditionalCacheHit();
        }

        @Override public void trackResponse(CacheStrategy cacheStrategy) {
            Cache.this.trackResponse(cacheStrategy);
        }
    }
    
    //能够经过下面两个构造函数构造一个Cache
    
    public Cache(File directory, long maxSize) {
        this(directory, maxSize, FileSystem.SYSTEM);
    }

    Cache(File directory, long maxSize, FileSystem fileSystem) {
        this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
    }

    //下面是主要方法
    
    @Nullable Response get(Request request) {
      	//...
    }

    @Nullable CacheRequest put(Response response) {
       //...
    }

    void remove(Request request) throws IOException {
        //...
    }

    void update(Response cached, Response network) {
      //...
    }
    
    synchronized void trackConditionalCacheHit() {
       //...
    }

    synchronized void trackResponse(CacheStrategy cacheStrategy) {
       //...
    }

    @Override public void flush() throws IOException {
       //...
    }

    @Override public void close() throws IOException {
        //...
    }
}
复制代码

Cache中有一个内部实现类InternalCache,见名知意,它是okhttp内部使用的,它实现了InternalCache接口,接口中的方法都和Cache中的方法同名,并且这个实现类的全部方法都是调用了Cache中相应的方法,也就是说InternalCache的方法实现和Cache相应的方法同样,但Cache和InternalCache不同的是,Cache比InternalCache多了一些方法供外部调用如flush()、 close()等,提供了更多对缓存的控制,而InternalCache中的方法都只是缓存的基本操做,如get、put、remove、update等方法,这些方法的逻辑都是基于Cache中的DiskLruCache实现,详情能够看DiskLruCache的原理实现。

要知道,okhttp默认是不使用缓存,也就是Cache为null,若是要使用缓存,咱们须要自行配置,经过下面方法使用okhttp的缓存机制:

//缓存的路径
File cacheDir = new File(Constant.PATH_NET_CACHE);
//这里经过带有两个参数的构造函数构造一个Cache
Cache cache = new Cache(cacheDir, 1024 * 1024 * 10);//缓存的最大尺寸10M

//而后设置给OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
    .cache(cache)
    .build();

复制代码

经过上面全局设置后,Cache和InternalCache都不会为null,由于在建立Cache时InternalCache也一块儿建立了,okhttp的缓存机制就会生效。

咱们先回到CacheInterceptor的intercept方法,它首先一开始就要判断cache是否等于null,那么CacheInterceptor的cache在哪里来的呢?是在构造函数中,以下:

public final class CacheInterceptor implements Interceptor {
    final @Nullable InternalCache cache;

    public CacheInterceptor(@Nullable InternalCache cache) {
        this.cache = cache;
    }
    //...
}
复制代码

可用看到它是InternalCache实例,在 getResponseWithInterceptorChain()中添加拦截器时就经过client为这个InternalCache赋值了,以下:

//RealCall.java
Response getResponseWithInterceptorChain() throws IOException {
    //...
    //添加负责缓存的拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //...
}
复制代码

注意到new CacheInterceptor(client.internalCache()),因此咱们看client的internalCache方法,以下:

//OkHttpClient.java
@Nullable InternalCache internalCache() {
    return cache != null ? cache.internalCache : internalCache;
  }
复制代码

cache就是上面全局设置的cache实例,因此不为null,返回cache中的internalCache实例,这样CacheInterceptor中就持有internalCache实例。

二、CacheStrategy - 缓存策略

CacheStrategy是okhttp缓存策略的实现,okhttp缓存策略遵循了HTTP缓存策略,所以了解okhttp缓存策略前须要有HTTP缓存相关基础:HTTP 协议缓存机制详解,了解了HTTP缓存策略后,咱们再来看CacheStrategy,以下:

public final class CacheStrategy {

    //CacheStrategy两个主要的成员变量:networkRequest、cacheResponse
    public final @Nullable Request networkRequest;
    public final @Nullable Response cacheResponse;

    CacheStrategy(Request networkRequest, Response cacheResponse) {
        this.networkRequest = networkRequest;
        this.cacheResponse = cacheResponse;
    }
    
    //...
    
    //经过工厂模式建立CacheStrategy
    public static class Factory {
        final long nowMillis;
        final Request request;
        final Response cacheResponse;

        public Factory(long nowMillis, Request request, Response cacheResponse) {
            this.nowMillis = nowMillis;
            this.request = request;
            this.cacheResponse = cacheResponse;
            //...
        }

        public CacheStrategy get() {
            CacheStrategy candidate = getCandidate();
            //...
            return candidate;
        }
        
         //...
    }
}
复制代码

CacheStrategy是经过工厂模式建立的,它有两个主要的成员变量:networkRequest、cacheResponse,CacheInterceptor的intercept方法经过CacheStrategy的networkRequest和cacheResponse的组合来判断执行什么策略,networkRequest是否为空决定是否请求网络,cacheResponse是否为空决定是否使用缓存,networkRequest和cacheResponse的4种组合和对应的缓存策略以下:

  • 一、networkRequest为null且cacheResponse为null:没有缓存使用,又不进行网络请求,构造状态码为504的Response。
  • 二、networkRequest为null但cacheResponse不为null:有缓存使用,且缓存在有效期内,因此直接返回缓存的Response。
  • 三、networkRequest不为null且cacheResponse不为null:有缓存使用,但缓存在客户端的判断中表示过时了,因此请求服务器进行决策,来决定是否使用缓存的Response。
  • 四、networkRequest不为null但cacheResponse为null:没有缓存使用,因此直接使用服务器返回的Response

networkRequest和cacheResponse在建立CacheStrategy时经过构造参数赋值,那么CacheStrategy在那里被建立呢?当调用CacheStrategy.Factory(long, Request, Response).get()时就会返回一个CacheStrategy实例,因此CacheStrategy在Factory的get方法中被建立,咱们来看Factory的get方法,以下:

//CacheStrategy.Factory
public CacheStrategy get() {
    CacheStrategy candidate = getCandidate();
    //...
    return candidate;
}
复制代码

能够看到CacheStrategy经过Factory的getCandidate方法建立,getCandidate方法以下:

//CacheStrategy.Factory
private CacheStrategy getCandidate() {
    //一、没有Response缓存,直接进行网络请求
    if (cacheResponse == null) {
        return new CacheStrategy(request, null);
    }

    //二、若是TLS握手信息丢失,直接进行网络请求
    if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
    }

    //三、根据Response状态码,Expired和Cache-Control的no-Store进行判断Response缓存是否可用
    if (!isCacheable(cacheResponse, request)) {
        //Response缓存不可用,直接进行网络请求
        return new CacheStrategy(request, null);
    }

    
    //得到Request的缓存控制字段CacheControl
    CacheControl requestCaching = request.cacheControl();
    //四、根据Request中的Cache-Control的noCache和header是否设置If-Modified-Since或If-None-Match进行判断是否可使用Response缓存
    if (requestCaching.noCache() || hasConditions(request)) {
        //不可使用Response缓存,直接进行网络请求
        return new CacheStrategy(request, null);
    }
    
 	//走到这里表示Response缓存可用

    //得到Response的缓存控制字段CacheControl
    CacheControl responseCaching = cacheResponse.cacheControl();

    //得到该Response已经缓存的时长
    long ageMillis = cacheResponseAge();
    //得到该Response能够缓存的时长
    long freshMillis = computeFreshnessLifetime();

    if (requestCaching.maxAgeSeconds() != -1) 
        //通常取max-age
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
    }

    long minFreshMillis = 0;
    if (requestCaching.minFreshSeconds() != -1) {
        //通常取0
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
    }

    long maxStaleMillis = 0;
    if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
        //取max-stale,
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
    }

	//五、判断缓存是否过时,决定是否使用Response缓存:Response已经缓存的时长 < max-stale + max-age
    if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
        if (ageMillis + minFreshMillis >= freshMillis) {
            builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
            builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
        }
        //5.一、缓存没有过时,直接使用该Response缓存
        return new CacheStrategy(null, builder.build());
    }

   //5.二、缓存过时了,判断是否设置了Etag或Last-Modified等标记
    String conditionName;
    String conditionValue;
    if (etag != null) {
        conditionName = "If-None-Match";
        conditionValue = etag;
    } else if (lastModified != null) {
        conditionName = "If-Modified-Since";
        conditionValue = lastModifiedString;
    } else if (servedDate != null) {
        conditionName = "If-Modified-Since";
        conditionValue = servedDateString;
    } else {
        //缓存没有设置Etag或Last-Modified等标记,因此直接进行网络请求
        return new CacheStrategy(request, null);
    }

	//缓存设置了Etag或Last-Modified等标记,因此添加If-None-Match或If-Modified-Since请求头,构造请求,交给服务器判断缓存是否可用
    Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
    Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

    Request conditionalRequest = request.newBuilder()
        .headers(conditionalRequestHeaders.build())
        .build();
    //networkRequest和cacheResponse都不为null
    return new CacheStrategy(conditionalRequest, cacheResponse);
}
复制代码

getCandidate()方法中根据HTTP的缓存策略决定networkRequest和cacheResponse的组合,从getCandidate()方法中咱们能够看到HTTP的缓存策略分为两种:

  • 一、强制缓存:客户端参与决策决定是否继续使用缓存,客户端第一次请求数据时,服务端返回了缓存的过时时间:Expires或Cache-Control,当客户端再次请求时,就判断缓存的过时时间,没有过时就能够继续使用缓存,不然就不使用,从新请求服务端。
  • 二、对比缓存:服务端参与决策决定是否继续使用缓存,客户端第一次请求数据时,服务端会将缓存标识:Last-Modified/If-Modified-Since、Etag/If-None-Match和数据一块儿返回给客户端 ,当客户端再次请求时,客户端将缓存标识发送给服务端,服务端根据缓存标识进行判断,若是缓存尚未更新,可使用,则返回304,表示客户端能够继续使用缓存,不然客户端不能继续使用缓存,只能使用服务器返回的新的响应。

并且强制缓存优先于对比缓存,咱们再贴出来自HTTP 协议缓存机制详解的一张图,它很好的解释了getCandidate()方法中1~5步骤流程,以下:

三、缓存机制

咱们再回到CacheInterceptor的intercept方法,它的1~4步骤就是CacheStrategy的networkRequest和cacheResponse的4种组合状况,都有详细的注释,每一种组合对应一种缓存策略,而缓存策略又是基于getCandidate()方法中写死的HTTP缓存策略,再结合okhttp本地缓存的实现Cache,咱们得出结论:okhttp的缓存机制 = Cache缓存实现 + 基于HTTP的缓存策略,整个流程图以下:

了解了okhttp的缓存机制后,咱们接着下一个拦截器ConnectInterceptor。

ConnectInterceptor

ConnectInterceptor的intercept(Chain)方法以下:

//ConnectInterceptor.java
@Override
public Response intercept(Chain chain) throws IOException {
   
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    //获取Transmitter
    Transmitter transmitter = realChain.transmitter();
	
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //一、新建一个Exchange
    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);

    //调用proceed方法,里面调用下一个拦截器CallServerInterceptor的intercept方法
    //这里调用的proceed方法是带有三个参数的,它传进了Request、Transmitter和刚刚新建的Exchange
    return realChain.proceed(request, transmitter, exchange);
}
复制代码

ConnectInterceptor的intercept(Chain)方法很简洁,里面定义了okhttp的链接机制,它首先获取Transmitter,而后经过Transmitter的newExchange方法建立一个Exchange,把它传到下一个拦截器CallServerInterceptor,Exchange是什么?Exchange负责从建立的链接的IO流中写入请求和读取响应,完成一次请求/响应的过程,在CallServerInterceptor中你会看到它真正的做用,这里先忽略。因此注释1的newExchange方法是链接机制的主要逻辑实现,咱们继续看Transmitter的newExchange方法,以下:

//Transmitter.java
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {

    //...省略异常处理

    //一、经过ExchangeFinder的find方法找到一个ExchangeCodec
    ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);

    //建立Exchange,并把ExchangeCodec实例codec传进去,因此Exchange内部持有ExchangeCodec实例
    Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);

    //...
    
    return result;
}
复制代码

重点是注释1,ExchangeFinder对象早在RetryAndFollowUpInterceptor中经过Transmitter的prepareToConnect方法建立,它的find方法是链接真正建立的地方,ExchangeFinder是什么?ExchangeFinder就是负责链接的建立,把建立好的链接放入链接池,若是链接池中已经有该链接,就直接取出复用,因此ExchangeFinder管理着两个重要的角色:RealConnection、RealConnectionPool,下面讲解一下RealConnectionPool和RealConnection,有助于链接机制的理解。

一、RealConnection - 链接实现

链接的真正实现,实现了Connection接口,内部利用Socket创建链接,以下:

public interface Connection {
    //返回这个链接使用的Route
    Route route();

    //返回这个链接使用的Socket
    Socket socket();

    //若是是HTTPS,返回TLS握手信息用于创建链接,不然返回null
    @Nullable Handshake handshake();

    //返回应用层使用的协议,Protocol是一个枚举,如HTTP1.一、HTTP2
    Protocol protocol();
}

public final class RealConnection extends Http2Connection.Listener implements Connection {

    public final RealConnectionPool connectionPool;
    //路由
    private final Route route;
    //内部使用这个rawSocket在TCP层创建链接
    private Socket rawSocket;
    //若是没有使用HTTPS,那么socket == rawSocket,不然这个socket == SSLSocket
    private Socket socket;
    //TLS握手
    private Handshake handshake;
    //应用层协议
    private Protocol protocol;
    //HTTP2链接
    private Http2Connection http2Connection;
    //okio库的BufferedSource和BufferedSink,至关于javaIO的输入输出流
    private BufferedSource source;
    private BufferedSink sink;


    public RealConnection(RealConnectionPool connectionPool, Route route) {
        this.connectionPool = connectionPool;
        this.route = route;
    }


    public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
        //...
    }

    //...
}
复制代码

RealConnection中有一个connect方法,外部能够调用该方法创建链接,connect方法以下:

//RealConnection.java
public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
    if (protocol != null) throw new IllegalStateException("already connected");

    RouteException routeException = null;
    List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
    ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);

    //路由选择
    if (route.address().sslSocketFactory() == null) {
      if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
        throw new RouteException(new UnknownServiceException(
            "CLEARTEXT communication not enabled for client"));
      }
      String host = route.address().url().host();
      if (!Platform.get().isCleartextTrafficPermitted(host)) {
        throw new RouteException(new UnknownServiceException(
            "CLEARTEXT communication to " + host + " not permitted by network security policy"));
      }
    } else {
      if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
        throw new RouteException(new UnknownServiceException(
            "H2_PRIOR_KNOWLEDGE cannot be used with HTTPS"));
      }
    }

    //开始链接
    while (true) {
      try {
        if (route.requiresTunnel()) {//若是是通道模式,则创建通道链接
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
          if (rawSocket == null) {
            // We were unable to connect the tunnel but properly closed down our resources.
            break;
          }
        } else {//一、不然进行Socket链接,大部分是这种状况
          connectSocket(connectTimeout, readTimeout, call, eventListener);
        }
        //创建HTTPS链接
        establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
        break;
      }
      //...省略异常处理

    if (http2Connection != null) {
      synchronized (connectionPool) {
        allocationLimit = http2Connection.maxConcurrentStreams();
      }
    }
  }
复制代码

咱们关注注释1,通常会调用connectSocket方法创建Socket链接,connectSocket方法以下:

//RealConnection.java
private void connectSocket(int connectTimeout, int readTimeout, Call call, EventListener eventListener) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    //根据代理类型的不一样建立Socket
    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    eventListener.connectStart(call, route.socketAddress(), proxy);
    rawSocket.setSoTimeout(readTimeout);
    try {
        //一、创建Socket链接
        Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    }
    //...省略异常处理

    try {
        //得到Socket的输入输出流
        source = Okio.buffer(Okio.source(rawSocket));
        sink = Okio.buffer(Okio.sink(rawSocket));
    } 
     //...省略异常处理
}
复制代码

咱们关注注释1,Platform是okhttp中根据不一样Android版本平台的差别实现的一个兼容类,这里就不细究,Platform的connectSocket方法最终会调用rawSocket的connect()方法创建其Socket链接,创建Socket链接后,就能够经过Socket链接得到输入输出流source和sink,okhttp就能够从source读取或往sink写入数据,source和sink是BufferedSource和BufferedSink类型,它们是来自于okio库,它是一个封装了java.io和java.nio的库,okhttp底层依赖这个库读写数据,Okio好在哪里?详情能够看这篇文章Okio好在哪

二、RealConnectionPool - 链接池

链接池,用来管理链接对象RealConnection,以下:

public final class RealConnectionPool {

    //线程池
    private static final Executor executor = new ThreadPoolExecutor(
        0 /* corePoolSize */,
        Integer.MAX_VALUE /* maximumPoolSize */, 
        60L /* keepAliveTime */, 
        TimeUnit.SECONDS,
        new SynchronousQueue<>(), 
        Util.threadFactory("OkHttp ConnectionPool", true));
 
    boolean cleanupRunning;
    //清理链接任务,在executor中执行
    private final Runnable cleanupRunnable = () -> {
        while (true) {
            //调用cleanup方法执行清理逻辑
            long waitNanos = cleanup(System.nanoTime());
            if (waitNanos == -1) return;
            if (waitNanos > 0) {
                long waitMillis = waitNanos / 1000000L;
                waitNanos -= (waitMillis * 1000000L);
                synchronized (RealConnectionPool.this) {
                    try {
                        //调用wait方法进入等待
                        RealConnectionPool.this.wait(waitMillis, (int) waitNanos);
                    } catch (InterruptedException ignored) {
                    }
                }
            }
        }
    };

    //双端队列,保存链接
    private final Deque<RealConnection> connections = new ArrayDeque<>();

    void put(RealConnection connection) {
        if (!cleanupRunning) {
            cleanupRunning = true;
            //使用线程池执行清理任务
            executor.execute(cleanupRunnable);
        }
        //将新建链接插入队列
        connections.add(connection);
    }

    long cleanup(long now) {
        //...
    }

    //...
}
复制代码

RealConnectionPool 在内部维护了一个线程池,用来执行清理链接任务cleanupRunnable,还维护了一个双端队列connections,用来缓存已经建立的链接。要知道建立一次链接要经历TCP握手,若是是HTTPS还要经历TLS握手,握手的过程都是耗时的,因此为了提升效率,就须要connections来对链接进行缓存,从而能够复用;还有若是链接使用完毕,长时间不释放,也会形成资源的浪费,因此就须要cleanupRunnable定时清理无用的链接,okhttp支持5个并发链接,默认每一个链接keepAlive为5分钟,keepAlive就是链接空闲后,保持存活的时间。

当咱们第一次调用RealConnectionPool 的put方法缓存新建链接时,若是cleanupRunnable还没执行,它首先会使用线程池执行cleanupRunnable,而后把新建链接放入双端队列,cleanupRunnable中会调用cleanup方法进行链接的清理,该方法返回如今到下次清理的时间间隔,而后调用wiat方法进入等待状态,等时间到了后,再次调用cleanup方法进行清理,就这样往复循环。咱们来看一下cleanup方法的清理逻辑:

//RealConnectionPool.java
long cleanup(long now) {
    
    int inUseConnectionCount = 0;//正在使用链接数
    int idleConnectionCount = 0;//空闲链接数
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;

    synchronized (this) {
        //遍历全部链接,记录空闲链接和正在使用链接各自的数量
        for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
            RealConnection connection = i.next();

            //若是该链接还在使用,pruneAndGetAllocationCount种经过引用计数的方式判断一个链接是否空闲
            if (pruneAndGetAllocationCount(connection, now) > 0) {
                //使用链接数加1
                inUseConnectionCount++;
                continue;
            }
            
            //该链接没有在使用

            //空闲链接数加1
            idleConnectionCount++;

            //记录keepalive时间最长的那个空闲链接
            long idleDurationNs = now - connection.idleAtNanos;
            if (idleDurationNs > longestIdleDurationNs) {
                longestIdleDurationNs = idleDurationNs;
                //这个链接极可能被移除,由于空闲时间太长
                longestIdleConnection = connection;
            }
        }
        
        //跳出循环后

        //默认keepalive时间keepAliveDurationNs最长为5分钟,空闲链接数idleConnectionCount最大为5个
        if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) {//若是longestIdleConnection的keepalive时间大于5分钟 或 空闲链接数超过5个
            //把longestIdleConnection链接从队列清理掉
            connections.remove(longestIdleConnection);
        } else if (idleConnectionCount > 0) {//若是空闲链接数小于5个 而且 longestIdleConnection链接还没到期清理
            //返回该链接的到期时间,下次再清理
            return keepAliveDurationNs - longestIdleDurationNs;
        } else if (inUseConnectionCount > 0) {//若是没有空闲链接 且 全部链接都还在使用
            //返回keepAliveDurationNs,5分钟后再清理
            return keepAliveDurationNs;
        } else {
            // 没有任何链接,把cleanupRunning复位
            cleanupRunning = false;
            return -1;
        }
    }

    //把longestIdleConnection链接从队列清理掉后,关闭该链接的socket,返回0,当即再次进行清理
    closeQuietly(longestIdleConnection.socket());

    return 0;
}
复制代码

从cleanup方法得知,okhttp清理链接的逻辑以下:

一、首先遍历全部链接,记录空闲链接数idleConnectionCount和正在使用链接数inUseConnectionCount,在记录空闲链接数时,还要找出空闲时间最长的空闲链接longestIdleConnection,这个链接是颇有可能被清理的;

二、遍历完后,根据最大空闲时长和最大空闲链接数来决定是否清理longestIdleConnection,

​ 2.一、若是longestIdleConnection的空闲时间大于最大空闲时长 或 空闲链接数大于最大空闲链接数,那么该链接就会被从队列中移除,而后关闭该链接的socket,返回0,当即再次进行清理;

​ 2.二、若是空闲链接数小于5个 而且 longestIdleConnection的空闲时间小于最大空闲时长即还没到期清理,那么返回该链接的到期时间,下次再清理;

​ 2.三、若是没有空闲链接 且 全部链接都还在使用,那么返回默认的keepAlive时间,5分钟后再清理;

​ 2.四、没有任何链接,idleConnectionCount和inUseConnectionCount都为0,把cleanupRunning复位,等待下一次put链接时,再次使用线程池执行cleanupRunnable。

了解了RealConnectionPool和RealConnection后,咱们再回到ExchangeFinder的find方法,这里是链接建立的地方。

三、链接建立(链接机制)

ExchangeFinder的fing方法以下:

//ExchangeFinder.java
public ExchangeCodec find( OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
   //...
    try {
        
      //调用findHealthyConnection方法,返回RealConnection
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,  writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
        
      return resultConnection.newCodec(client, chain);  
    }
    //...省略异常处理
  }

 
  private RealConnection findHealthyConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks) throws IOException {
     //一个死循环
    while (true) {
        
       //调用findConnection方法,返回RealConnection
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          pingIntervalMillis, connectionRetryEnabled);
        
	  //...

        //判断链接是否可用
        if (!candidate.isHealthy(doExtensiveHealthChecks)) {
            candidate.noNewExchanges();
            continue;
        }

      return candidate;
    }
  
复制代码

ExchangeFinder的find方法会调用findHealthyConnection方法,里面会不断调用findConnection方法,直到找到一个可用的链接返回。ExchangeFinder的findConnection方法以下:

//ExchangeFinder.java
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;//返回结果,可用的链接
    Route selectedRoute = null;
    RealConnection releasedConnection;
    Socket toClose;
    synchronized (connectionPool) {
       if (transmitter.isCanceled()) throw new IOException("Canceled");
      hasStreamFailure = false; .

	 //一、尝试使用已经建立过的链接,已经建立过的链接可能已经被限制建立新的流
      releasedConnection = transmitter.connection;
      //1.一、若是已经建立过的链接已经被限制建立新的流,就释放该链接(releaseConnectionNoEvents中会把该链接置空),并返回该链接的Socket以关闭
      toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
          ? transmitter.releaseConnectionNoEvents()
          : null;

        //1.二、已经建立过的链接还能使用,就直接使用它看成结果、
        if (transmitter.connection != null) {
            result = transmitter.connection;
            releasedConnection = null;
        }

        //二、已经建立过的链接不能使用
        if (result == null) {
            //2.一、尝试从链接池中找可用的链接,若是找到,这个链接会赋值先保存在Transmitter中
            if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
                //2.二、从链接池中找到可用的链接
                foundPooledConnection = true;
                result = transmitter.connection;
            } else if (nextRouteToTry != null) {
                selectedRoute = nextRouteToTry;
                nextRouteToTry = null;
            } else if (retryCurrentRoute()) {
                selectedRoute = transmitter.connection.route();
            }
        }
    }
	closeQuietly(toClose);
    
	//...
    
    if (result != null) {
        //三、若是在上面已经找到了可用链接,直接返回结果
        return result;
    }
    
    //走到这里没有找到可用链接

    //看看是否须要路由选择,多IP操做
    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
        newRouteSelection = true;
        routeSelection = routeSelector.next();
    }
    List<Route> routes = null;
    synchronized (connectionPool) {
        if (transmitter.isCanceled()) throw new IOException("Canceled");

        //若是有下一个路由
        if (newRouteSelection) {
            routes = routeSelection.getAll();
            //四、这里第二次尝试从链接池中找可用链接
            if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, false)) {
                //4.一、从链接池中找到可用的链接
                foundPooledConnection = true;
                result = transmitter.connection;
            }
        }

        //在链接池中没有找到可用链接
        if (!foundPooledConnection) {
            if (selectedRoute == null) {
                selectedRoute = routeSelection.next();
            }

           //五、因此这里新建立一个链接,后面会进行Socket链接
            result = new RealConnection(connectionPool, selectedRoute);
            connectingConnection = result;
        }
    }

    // 4.二、若是在链接池中找到可用的链接,直接返回该链接
    if (foundPooledConnection) {
        eventListener.connectionAcquired(call, result);
        return result;
    }

    //5.一、调用RealConnection的connect方法进行Socket链接,这个在RealConnection中讲过
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener);
    
    connectionPool.routeDatabase.connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
        connectingConnection = null;
        //若是咱们刚刚建立了同一地址的多路复用链接,释放这个链接并获取那个链接
        if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
            result.noNewExchanges = true;
            socket = result.socket();
            result = transmitter.connection;
        } else {
            //5.二、把刚刚新建的链接放入链接池
            connectionPool.put(result);
            //5.三、把刚刚新建的链接保存到Transmitter的connection字段
            transmitter.acquireConnectionNoEvents(result);
        }
    }
    
    closeQuietly(socket);
    eventListener.connectionAcquired(call, result);
    
    //5.四、返回结果
    return result;
}
复制代码

这个findConnection方法就是整个ConnectInterceptor的核心,咱们忽略掉多IP操做和多路复用(HTTP2),假设如今咱们是第一次请求,链接池和Transmitter中没有该链接,因此跳过一、二、3,直接来到5,建立一个新的链接,而后把它放入链接池和Transmitter中;接着咱们用同一个Call进行了第二次请求,这时链接池和Transmitter中有该链接,因此就会走一、二、3,若是Transmitter中的链接还可用就返回,不然从链接池获取一个可用链接返回,因此整个链接机制的大概过程以下:

Transmitter中的链接和链接池中的链接有什么区别?咱们知道每建立一个Call,就会建立一个对应的Transmitter,一个Call能够发起屡次请求(同步、异步),不一样的Call有不一样的Transmitter,链接池是在建立OkhttpClient时建立的,因此链接池是全部Call共享的,即链接池中的链接全部Call均可以复用,而Transmitter中的那个链接只是对应它相应的Call,只能被本次Call的全部请求复用。

了解了okhttp的链接机制后,咱们接着下一个拦截器CallServerInterceptor。

CallServerInterceptor

CallServerInterceptor的intercept(Chain)方法以下:

//CallServerInterceptor.java
@Override
public Response intercept(Chain chain) throws IOException {
    
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    //获取Exchange
    Exchange exchange = realChain.exchange();
    //获取Request
    Request request = realChain.request();

	//经过Exchange的writeRequestHeaders(request)方法写入请求的header
    exchange.writeRequestHeaders(request);

    boolean responseHeadersStarted = false;
    Response.Builder responseBuilder = null;
    
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
        //...
        if (responseBuilder == null) {
            //经过okio写入请求的body
            if (request.body().isDuplex()) {
                exchange.flushRequest();
                BufferedSink bufferedRequestBody = Okio.buffer(
                    exchange.createRequestBody(request, true));
                request.body().writeTo(bufferedRequestBody);
            } else {
                BufferedSink bufferedRequestBody = Okio.buffer(
                    exchange.createRequestBody(request, false));
                request.body().writeTo(bufferedRequestBody);
                bufferedRequestBody.close();
            }
        } else {
           //...
        }
    } else {
      exchange.noRequestBody();
    }

    //...
    
    //下面开始获取网络请求返回的响应
    
    //经过Exchange的readResponseHeaders(boolean)方法读取响应的header
    if (responseBuilder == null) {
        responseBuilder = exchange.readResponseHeaders(false);
    }
    
    //获取响应后,经过Builder模式构造Response
    Response response = responseBuilder
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    //...
    
    //构造Response的body
    if (forWebSocket && code == 101) {
        //构造一个空的body的Response
        response = response.newBuilder()
            .body(Util.EMPTY_RESPONSE)
            .build();
    } else {
        //经过Exchange的openResponseBody(Response)方法读取响应的body,而后经过响应的body继续构造Response
        response = response.newBuilder()
            .body(exchange.openResponseBody(response))
            .build();
    }
    
    //...

    //返回响应Response
    return response;
  }
复制代码

在ConnectInterceptor中咱们已经创建了链接,链接到了服务器,获取了输入输出流,因此CallServerInterceptor的intercept(Chain)方法逻辑就是把请求发送到服务器,而后获取服务器的响应,以下:

一、发送请求:

​ 1.一、经过Exchange的writeRequestHeaders(request)方法写入请求的header;

​ 1.二、若是请求的body不为空,经过okio写入请求的body。

二、获取响应:

​ 2.一、经过Exchange的readResponseHeaders(boolean)方法读取响应的header;

​ 2.二、经过Exchange的openResponseBody(Response)方法读取响应的body。

这个发送获取的过程经过Exchange进行,前面已经讲过它在ConnectInterceptor中建立,在process方法中传进来,因此这里能够经过Chain获取Exchange,Exchange它是负责从IO流中写入请求和读取响应,完成一次请求/响应的过程,它内部的读写都是经过一个ExchangeCodec类型的codec来进行,而ExchangeCodec内部又是经过Okio的BufferedSource和BufferedSink进行IO读写,这个过程在上一篇文章已经分析过了,这里不在累述。

结语

结合上一篇文章,咱们对okhttp已经有了一个深刻的了解,首先,咱们会在请求的时候初始化一个Call的实例,而后执行它的execute()方法或enqueue()方法,内部最后都会执行到getResponseWithInterceptorChain()方法,这个方法里面经过拦截器组成的责任链,依次通过用户自定义普通拦截器、重试拦截器、桥接拦截器、缓存拦截器、链接拦截器和用户自定义网络拦截器和访问服务器拦截器等拦截处理过程,来获取到一个响应并交给用户。okhttp的请求流程、缓存机制和链接机制是当中的重点,在阅读源码的过程当中也学习到不少东西,下一次就来分析它的搭档Retrofit。

相关文章
相关标签/搜索