美文网首页
OkHttp讲解(二)

OkHttp讲解(二)

作者: 涛涛123759 | 来源:发表于2020-06-11 17:08 被阅读0次

OkHttp讲解(一)
OkHttp讲解(二)
OkHttp讲解(三)

1、拦截器 Interceptor

1.1 okhttp的工作流程图


可以看出,Interceptor贯穿了整个请求过程,是在请求执行过程中扮演重要角色。
这是okhttp的请求执行过程,从应用发出request,到应用收到response,期间经历了N个拦截器。

  • 蓝色块上方是APPLication Interceptor,也就是应用程序拦截器,即开发者自己自定义的拦截器,代码中的client.interceptors()获取的就是这类拦截器
  • 蓝色块下方是NetWork Interceptor,也就是网络拦截器,用来观察单个网络请求和响应,只能调用一次proceed方法
  • 蓝色块代表的就是OKHttp提供的拦截器,共有5个,也是我们需要关注的重点

1.2 拦截器的分类

okhttp工作流程图中,橙色框框内的那些拦截器,属于okhttp库内部定义的,一般情况下不会更改。所以这里只讨论开发者能够自定义的拦截器。
  分为两类:
  1)ApplicationInterceptor(应用拦截器)
  2)NetworkInterceptor(网络拦截器)

相同点

  • 都能对server返回的response进行拦截
  • 这两种拦截器本质上都是基于Interceptor接口,由开发者实现这个接口,然后将自定义的Interceptor类的对象设置到okhttpClient对象中。所以,他们的对象,本质上没什么不同,都是Interceptor的实现类的对象。
  • 两者都会被add到OkHttpClient内的一个ArrayList中。当请求执行的时候,多个拦截器会依次执行(list本身就是有序的)。

不同点

  • okhttpClient添加两种拦截器的api不同。添加应用拦截器的接口是addInterceptor(),而添加网络拦截器的接口是addNetworkInterceptor().
  • 两者负责的区域不同,从最上方图中可以看出,应用拦截器作用于okhttpCore到Application之间,网络拦截器作用于 network和okhttpCore之间
  • 在某种特殊情况下(比如:访问了一个url,结果这个url发生了重定向),网络拦截器有可能被执行多次,但是 不论任何情况,application只会被执行一次。

1.3 okhttp库内部定义的拦截器

  • RealCall
    在okhttp框架中,当客户端通过OkHttpClient发起同步或异步请求时,okhttp框架将会创建一个RealCall,这个实例将根据客户端提供的Request,发起同步或异步网络请求操作,在RealCall被创建时,将会创建一个Interceptor的具体实现。我们知道,okhttp框架将网络请求的步骤,通过Interceptor接口进行了统一的分层式设计,将每个环节都分成了不同的Interceptor,Interceptor又被称为拦截器,这是该网络框架设计的精髓所在,通过不同的拦截器规则,处理网络请求过程中的不同环节,最终通过链式调用,实现一个完整的网络请求操作。
  Response getResponseWithInterceptorChain() throws IOException {
    List<Interceptor> interceptors = new ArrayList<>();
    //添加开发者应用层自定义的Interceptor
    interceptors.addAll(client.interceptors());
    //这个Interceptor是处理请求失败的重试,重定向    
    interceptors.add(retryAndFollowUpInterceptor);
    //这个Interceptor工作是添加一些请求的头部或其他信息
    //并对返回的Response做一些友好的处理(有一些信息你可能并不需要)
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //这个Interceptor的职责是判断缓存是否存在,读取缓存,更新缓存等等
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //这个Interceptor的职责是建立客户端和服务器的连接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //添加开发者自定义的网络层拦截器
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //一个包裹这request的chain
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    //把chain传递到第一个Interceptor手中
    return chain.proceed(originalRequest);
  }
  • RetryAndFollowUpInterceptor 重试和失败重定向拦截器
    这个拦截器它的作用主要是负责请求的重定向操作,用于处理网络请求中,请求失败后的重试链接。把StreamAllocation对象,传递给后面的拦截器。
private static final int MAX_FOLLOW_UPS = 20;

从这个静态变量可以看出 RetryAndFollowUpInterceptor 失败重连最多20次。

 @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    //创建一个新的流
    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);
    //重定向次数
    int followUpCount = 0;
     // 上一个重试得到的响应
    Response priorResponse = null;
    while (true) {
      //如果RealCall调用了cancel,即取消请求,那就释放资源,抛出异常结束请求
      if (canceled) {
          //如果取消了则删除连接上的call请求
        streamAllocation.release();
        throw new IOException("Canceled");
      }
      // 定义请求的响应
      Response response = null;
      //// 是否释放连接,默认为true
      boolean releaseConnection = true;
      try {
        //调用下一个拦截器 即BridgeInterceptor;进行网络连接,获取response
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
        // 如果没有发送异常,修改标志 不需要重试
        releaseConnection = false;
      } catch (RouteException e) {
        //出现路由连接异常,通过recover方法判断能否恢复连接,如果不能将抛出异常不再重试
        //recover(...)检测连接是否还可以继续
        if (!recover(e.getLastConnectException(), false, request)) {
          throw e.getLastConnectException();
        }
        //能恢复连接,修改标志 不释放连接
        releaseConnection = false;
          //回到下一次循环 继续重试 除了finally代码外,下面的代码都不会执行
        continue;
      } catch (IOException e) {//后续拦截器在与服务器通信中抛出IO异常
        //判断该异常是否是连接关闭异常
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        ////通过recover方法判断能否恢复连接,如果不能将抛出异常不再重试
        if (!recover(e, requestSendStarted, request)) throw e;
        //能恢复连接, 修改标志 不释放连接
        releaseConnection = false;
        //回到下一次循环 继续重试 除了finally代码外,下面的代码都不会执行
        continue;
      } finally {
        // 如果releaseConnection为true,说明后续拦截器抛出了其它异常,那就释放所有资源,结束请求
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

      // 走到这里,说明网络请求已经完成了,但是响应码并不一定是200
      // 可能是其它异常的响应码或者重定向响应码
      
      // 如果priorResponse 不等于null,说明前面已经完成了一次请求
      // 那就通过上一次的response构建新的response,但是body为null.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }
      //对response进行响应码的判断,如果需要进行重定向,那就获取新的Request
      Request followUp = followUpRequest(response);
      // 如果为null,那就没必要重新请求,说明已经有了合适的Response,直接返回
      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }
       //关闭,忽略任何已检查的异常
      closeQuietly(response.body());
      //检测重连次数是否超过20次,如果超过就抛出异常,避免消耗客户端太多资源
      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
      //如果该请求体被UnrepeatableRequestBody标记,则不可重试
      if (followUp.body() instanceof UnrepeatableRequestBody) {
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }
      //判断重连前的Request与重新构建的Request是否有相同的连接,即host、port、scheme是否一致
      if (!sameConnection(response, followUp.url())) {
        // 如果不是相同的url连接,先释放之间的,再创建新的StreamAllocation
        streamAllocation.release();
        streamAllocation = new StreamAllocation(
            client.connectionPool(), createAddress(followUp.url()), callStackTrace);
      } else if (streamAllocation.codec() != null) {
         // 如果相同,但是本次请求的流没有关闭,那就抛出异常
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }
       //把重定向的请求赋值给request,以便再次进入循环执行
      request = followUp;
      priorResponse = response;
    }
  }
 /**
  * 判断当与服务器通信失败时,连接能否进行恢复
  * 返回true,表示可以进行恢复
  * 返回false 表示不能恢复,即不能重连
  */
  private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
    //根据抛出的异常,做出连接、连接路线的一些处理,并且释放连接,关闭连接
    streamAllocation.streamFailed(e);
   
    // 判断开发者是否禁用了失败重连
    // 在构建OKHttpClient的时候可以通过build进行配置
    //如果禁用,那就返回false,不进行重连
    if (!client.retryOnConnectionFailure()) return false;

    // 如果不是连接关闭异常,且请求体被UnrepeatableRequestBody标记,那不能恢复
    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;

    // 根据异常判断是否可以重连
    if (!isRecoverable(e, requestSendStarted)) return false;

    // 判断还有没有多余线路进行连接
    // 如果没有,返回false
    if (!streamAllocation.hasMoreRoutes()) return false;

    // 走到这里说明可以恢复连接,尝试重连
    return true;
  }
  • BridgeInterceptor 桥接和适配拦截器
    主要是补充用户创建请求当中缺少的一些必要的请求头。BridgeInterceptor 为用户构建的一个 Request 请求转化为能够进行网络访问的请求,同时将网络请求回来的响应 Response 转化为用户可用的 Response。比如,涉及的网络文件的类型和网页的编码,返回的数据的解压处理等等。
@Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    //组织Request Header包括这是keep-alive, Cookie添加,gzip等
    ....
    //传递
    Response networkResponse = chain.proceed(requestBuilder.build());
    //组织Response Header 包括cookie保存更新,Gzip解压等
    ....
    return responseBuilder.build();
  }
  • CacheInterceptor 缓存拦截器
    如果当前未使用网络,并且缓存不可以使用,通过构建者模式创建一个Response响应,抛出504错误
    。如果有缓存 但是不能使用网络 ,直接返回缓存结果。这是在进行网络请求之前所做的事情,当网络请求完成,得到下一个拦截器返回的response之后,判断response的响应码是否是HTTP_NOT_MODIFIED = 304,(未改变)是则从缓存中读取数据。
 @Override 
  public Response intercept(Chain chain) throws IOException {
    //通过request从缓存中获取响应
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
  /**
    * CacheStrategy 是一个缓存策略类 比如强制缓存 对比缓存等 它决定是使用缓存还是进行网络请求
    * 其内部维护了Request、Response
    * 如果Request为null表示不使用网络
    * 如果Response为null表示不使用缓存
    */
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    //根据缓存策略获取缓存Request和Response
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
    //根据缓存策略,更新统计指标:请求次数、使用网络请求次数、使用缓存次数
    if (cache != null) {
      cache.trackResponse(strategy);
    }
     // 能从缓存中获取响应但是缓存策略是不使用缓存,那就关闭获取的缓存
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // 根据策略,不使用网络,又没有缓存的直接报错,并返回错误码504
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // 如果缓存策略是不使用网络但是可以使用缓存,那就通过缓存策略的缓存构建响应并返回
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
      //缓存不可用或者缓存过期,网络获取
    Response networkResponse = null;
    try {
      //前面两个都没有返回,继续执行下一个Interceptor,即ConnectInterceptor
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // 如果发生了IO异常或者其它异常,关闭缓存避免内存泄漏
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // 如果缓存策略是可以使用缓存
    if (cacheResponse != null) {
      // 且网络响应码是304 HTTP_NOT_MODIFIED说明本地缓存可以使用
      // 且网络响应是没有响应体的
      // 这时候就合并缓存响应和网络响应并构建新的响应
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        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();
        // 在合并标头之后但在剥离Content-Encoding标头之前更新缓存
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
    // 走到这里说明缓存策略是不可以使用缓存或本地缓存不可用
    // 那就通过网络响应构建响应对象
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
    //对数据进行缓存
    if (cache != null) {
      // 如果响应有响应体且响应可以缓存 那就将响应写入到缓存
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // 缓存响应的部分信息
        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.
        }
      }
    }
    //更新缓存
    return response;
  }
  • ConnectInterceptor 连接拦截器
    在 okhttp底层是通过 socket 的方式于服务端进行连接的,并且在连接建立之后会通过 okio 获取通向 server 端的输入流 Source 和输出流 Sink。
@Override public Response intercept(Chain chain) throws IOException {
   RealInterceptorChain realChain = (RealInterceptorChain) chain;
   Request request = realChain.request();
    //获取可复用流
   StreamAllocation streamAllocation = realChain.streamAllocation();
   boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //创建输出流
   HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
   //根据HTTP/1.x(keep-alive)和HTTP/2(流复用)的复用机制,发起连接
   RealConnection connection = streamAllocation.connection();
   return realChain.proceed(request, streamAllocation, httpCodec, connection);
 }
  • CallServerInterceptor 网络服务拦截器,OkHttp核心拦截器,网络交互的关键
    主要负责将请求写入到 IO 流当中,并且从 IO 流当中获取服务端返回给客服端的响应数据。
    CallServerInterceptor 在 ConnectInterceptor 拦截器的功能就是负责与服务器建立 Socket 连接,并且创建了一个 HttpStream 它包括通向服务器的输入流和输出流。而接下来的 CallServerInterceptor 拦截器的功能使用 HttpStream 与服务器进行数据的读写操作的
    okhttp的拦截器就是在intercept(Chain chain)的回调中对Request和Response进行修改,然后直接返回了response 而不是进行继续递归,具体执行RealConnection里面是通过OKio实现的。在okhttp中,网络连接也是一个拦截器(CallServerInterceptor),他是最后一个被调用的,负责将request写入网络流中,并从网络流中读取服务器返回的信息写入Response中返回给客户端。
@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();
    //发送请求的时间戳
    long sentRequestMillis = System.currentTimeMillis();
    //写入请求头信息
    httpCodec.writeRequestHeaders(request);
     //发送header数据
    httpCodec.writeRequestHeaders(request);
    Response.Builder responseBuilder = null;
    //根据是否支持100-continue,发送body数据
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    ...
    }
    //结束请求
    httpCodec.finishRequest();
    if (responseBuilder == null) {
      //读取响应头信息
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        //发送请求的时间
        .sentRequestAtMillis(sentRequestMillis)
        //接收到响应的时间
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

   int code = response.code();
    //response处理
    ...
    return response;
  }

那我们再来看下OkHttp网络请求的整体接口图


2、 Interceptor 关联类分析

2.1 StreamAllocation 的成员变量

简介
StreamAllocation是用来协调Connections、Streams和Calls这三个实体的。

  • Connections:连接到远程服务器的物理套接字,这个套接字连接可能比较慢,所以它有一套取消机制。
  • Streams:定义了逻辑上的HTTP请求/响应对,每个连接都定义了它们可以携带的最大并* 发流,HTTP/1.x每次只可以携带一个,HTTP/2每次可以携带多个。
    Calls:定义了流的逻辑序列,这个序列通常是一个初始请求以及它的重定向请求,对于同一个连接,我们通常将所有流都放在一个调用中,以此来统一它们的行为。

HTTP通信 执行 网络请求Call 需要在 连接Connection 上建立一个新的 流Stream,我们将 StreamAllocation 称之 流 的桥梁,它负责为一次 请求 寻找 连接 并建立 流,从而完成远程通信。

   public final Address address;//地址
  private Route route; //路由
  private final ConnectionPool connectionPool;  //连接池
  private final Object callStackTrace; //日志

  // State guarded by connectionPool.
  private final RouteSelector routeSelector; //路由选择器
  private int refusedStreamCount;  //拒绝的次数
  private RealConnection connection;  //连接
  private boolean released;  //是否已经被释放
  private boolean canceled  //是否被取消了

比较重要的方法, 通过ConnectInterceptor 中的HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);方法调用

  public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
    int connectTimeout = client.connectTimeoutMillis();
    int readTimeout = client.readTimeoutMillis();
    int writeTimeout = client.writeTimeoutMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
      //获取一个连接
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
   //实例化HttpCodec,如果是HTTP/2则是Http2Codec否则是Http1Codec
      HttpCodec resultCodec = resultConnection.newCodec(client, this);

      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }

2.2 RealConnection

  • okhttp是底层实现框架,与httpURLconnection是同一级别的。OKHttp底层建立网络连接的关键就是RealConnection类。RealConnection类底层封装socket,是真正的创建连接者。分析这个类之后就明白了OKHttp与httpURLconnection的本质不同点。
  • RealConnection是Connection的实现类,代表着链接socket的链路,如果拥有了一个RealConnection就代表了我们已经跟服务器有了一条通信链路,而且通过RealConnection代表是连接socket链路,RealConnection对象意味着我们已经跟服务端有了一条通信链路了,在这个类里面实现的三次握手。
  • 在OKHttp里面,记录一次连接的是RealConnection,这个负责连接,在这个类里面用socket来连接,用HandShake来处理握手。
  //链接的线程池
  private final ConnectionPool connectionPool;
  private final Route route;
  //下面这些字段,通过connect()方法开始初始化,并且绝对不会再次赋值
  /** The low-level TCP socket. */
  private Socket rawSocket; //底层socket
  private Socket socket;  //应用层socket
  //握手
  private Handshake handshake;
   //协议
  private Protocol protocol;
   // http2的链接
  private Http2Connection http2Connection;
  //通过source和sink,大家可以猜到是与服务器交互的输入输出流
  private BufferedSource source;
  private BufferedSink sink;
  //下面这个字段是 属于表示链接状态的字段,并且有connectPool统一管理
  //如果noNewStreams被设为true,则noNewStreams一直为true,不会被改变,并且表示这个链接不会再创新的stream流
  public boolean noNewStreams;
  //成功的次数
  public int successCount;
  //此链接可以承载最大并发流的限制,如果不超过限制,可以随意增加
  public int allocationLimit = 1;

RealConnection的connect方法,connect()里面进行了三次握手

public void connect(。。。) {
     //如果协议不等于null,抛出一个异常
    if (protocol != null) throw new IllegalStateException("already connected");

   。。 省略部分代码。。。。

    while (true) {//一个while循环
         //如果是https请求并且使用了http代理服务器
        if (route.requiresTunnel()) {
          connectTunnel(...);
        } else {//
            //直接打开socket链接
          connectSocket(connectTimeout, readTimeout);
        }
        //建立协议
        establishProtocol(connectionSpecSelector);
        break;//跳出while循环
        。。省略部分代码。。。
  }

 //当前route的请求是https并且使用了Proxy.Type.HTTP代理
 public boolean requiresTunnel() {
    return address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP;
  }

普通连接的建立过程为建立TCP连接,建立TCP连接的过程为

 private void connectSocket(int connectTimeout, int readTimeout) 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);

    rawSocket.setSoTimeout(readTimeout);
    try {
    //连接socket
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    } catch (ConnectException e) {
      throw new ConnectException("Failed to connect to " + route.socketAddress());
    }
    source = Okio.buffer(Okio.source(rawSocket));//从socket中获取source 对象。
    sink = Okio.buffer(Okio.sink(rawSocket));//从socket中获取sink 对象。
  }

Okio.source(rawSocket)Okio.sink(rawSocket)的原码

  public static Source source(Socket socket) throws IOException {
    if (socket == null) throw new IllegalArgumentException("socket == null");
    AsyncTimeout timeout = timeout(socket);
    Source source = source(socket.getInputStream(), timeout);
    return timeout.source(source);
  }

  public static Sink sink(Socket socket) throws IOException {
    if (socket == null) throw new IllegalArgumentException("socket == null");
    AsyncTimeout timeout = timeout(socket);
    Sink sink = sink(socket.getOutputStream(), timeout);
    return timeout.sink(sink);
  }

建立隧道连接的过程

  private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout)
      throws IOException {
      //1、创建隧道请求对象
    Request tunnelRequest = createTunnelRequest();
    HttpUrl url = tunnelRequest.url();
    int attemptedConnections = 0;
    int maxAttempts = 21;
    //一个while循环
    while (true) {
       //尝试连接词说超过最大次数
      if (++attemptedConnections > maxAttempts) {
        throw new ProtocolException("Too many tunnel connections attempted: " + maxAttempts);
      }
      //2、打开socket链接
      connectSocket(connectTimeout, readTimeout);
     //3、请求开启隧道并返回tunnelRequest 
      tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);

     //4、成功开启了隧道,跳出while循环
      if (tunnelRequest == null) break; /

      //隧道未开启成功,关闭相关资源,继续while循环    
      //当然,循环次数超限后抛异常,退出wiile循环
      closeQuietly(rawSocket);
      rawSocket = null;
      sink = null;
      source = null;
    }
  }
  //隧道请求是一个常规的HTTP请求,只是请求的内容有点特殊。最初创建的隧道请求如
  private Request createTunnelRequest() {
    return new Request.Builder()
        .url(route.address().url())
        .header("Host", Util.hostHeader(route.address().url(), true))
        .header("Proxy-Connection", "Keep-Alive") // For HTTP/1.0 proxies like Squid.
        .header("User-Agent", Version.userAgent())
        .build();
  }

2.3 ConnectionPool

管理http和http/2的链接,以便减少网络请求延迟。同一个address将共享同一个connection。该类实现了复用连接的目标。

//这是一个用于清楚过期链接的线程池,每个线程池最多只能运行一个线程,并且这个线程池允许被垃圾回收
  private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

  /** The maximum number of idle connections for each address. */
  //每个address的最大空闲连接数。
  private final int maxIdleConnections;
  private final long keepAliveDurationNs;
  //清理任务
  private final Runnable cleanupRunnable = new Runnable() {
    @Override public void run() {
      while (true) {
        long waitNanos = cleanup(System.nanoTime());
        if (waitNanos == -1) return;
        if (waitNanos > 0) {
          long waitMillis = waitNanos / 1000000L;
          waitNanos -= (waitMillis * 1000000L);
          synchronized (ConnectionPool.this) {
            try {
              ConnectionPool.this.wait(waitMillis, (int) waitNanos);
            } catch (InterruptedException ignored) {
            }
          }
        }
      }
    }
  };
  //链接的双向队列
  private final Deque<RealConnection> connections = new ArrayDeque<>();
  //路由的数据库
  final RouteDatabase routeDatabase = new RouteDatabase();
   //清理任务正在执行的标志
  boolean cleanupRunning;
//创建一个适用于单个应用程序的新连接池。
 //该连接池的参数将在未来的okhttp中发生改变
 //目前最多可容乃5个空闲的连接,存活期是5分钟
  public ConnectionPool() {
    this(5, 5, TimeUnit.MINUTES);
  }

  public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    this.maxIdleConnections = maxIdleConnections;
    this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

    // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
    //保持活着的时间,否则清理将旋转循环
    if (keepAliveDuration <= 0) {
      throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
    }
  }
  • 1、主要就是connections,可见ConnectionPool内部以队列方式存储连接;
  • 2、routDatabase是一个黑名单,用来记录不可用的route,但是看代码貌似ConnectionPool并没有使用它。所以此处不做分析。
  • 3、剩下的就是和清理有关了,所以executor是清理任务的线程池,cleanupRunning是清理任务的标志,cleanupRunnable是清理任务。

2.4 RealInterceptorChain 拦截器链

  • 当发送一个请求的时候,实质OkHttp会通过一个拦截器的链来执行OkHttp的请求。
  • 这就是所谓的拦截器链,执行 RetryAndFollowUpInterceptor => 执行 BridgeInterceptor => 执行 CacheInterceptor => 执行 ConnectInterceptor => 执行 CallServerInterceptor => 响应到
    ConnectInterceptor => 响应到 CacheInterceptor => 响应到 BridgeInterceptor => 响应到 RetryAndFollowUpInterceptor
public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  //在RetryAndFollowUpInterceptor中new的
  private final StreamAllocation streamAllocation;
  //在ConnectInterceptor中new的
  private final HttpCodec httpCodec;
  //在ConnectInterceptor中new的
  private final RealConnection connection;
  //标识应该取拦截器链表里面的第几个拦截器
  private final int index;  //通过index + 1
  private int calls;  //通过call++
  private final Request request;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, RealConnection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
  }

  @Override public Connection connection() {
    return connection;
  }

  public StreamAllocation streamAllocation() {
    return streamAllocation;
  }

  public HttpCodec httpStream() {
    return httpCodec;
  }

  @Override public Request request() {
    return request;
  }
  //执行继续拦截操作
  @Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }
/**
  * 依次取出拦截器链表中的每个拦截器去获取Response
  */
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
     // 1、迭代拦截器集合
    if (index >= interceptors.size()) throw new AssertionError();
     //2、记录本方法调用次数,创建一次实例,call加1
    calls++;

    //如果已经为该Request创建了stream,就不再继续创建了
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    //  如果已经为该Request创建了stream,那该方法只能调用一次
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

   //创建新的拦截器链对象, 并将计数器+1
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    //取出下一个拦截器
    Interceptor interceptor = interceptors.get(index);
    //执行拦截器的intercept方法获取结果,并将新的拦截器链对象传入
    Response response = interceptor.intercept(next);

    // 确保该方法只能调用一次
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }
}

相关文章

网友评论

      本文标题:OkHttp讲解(二)

      本文链接:https://www.haomeiwen.com/subject/tvmutktx.html