美文网首页
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