流行框架源码分析(8)-OkHttp源码分析

主目录见:Android高级进阶知识(这是总目录索引)
 OkHttp的知识点实在是不少,优秀的思想也有很多,这里只能说尽量会提到但是没有把握非常详细,要读懂Okhttp的代码需要的知识还比较多,大家做好准备呀,如果提到的知识不懂,可以另外查查,不然这篇文章就会太长了,同时这里借鉴一张整体流程图:

OkHttp流程图

这张流程图还是非常详尽的,再结合我们代码的解析,应该能了解整体的过程,当然中间还会说明一些优秀的思想,如果要看一些类的详细介绍也可以参考[官方的wiki]

一.目标

 首先看OkHttp的源码主要是为了了解网络相关框架的一些机制,发散我们的思维,同时让我们用这个框架的时候更能得心应手,同时我们今天目标有如下:
1.为后面的retrofit的讲解做铺垫,毕竟retrofit底层用的访问网络框架是okhttp;
2.了解一些网络框架优秀的设计思想,以拓展我们的知识。

二.源码分析

首先我们这里也是跟前面的文章一样,先来看看基础的用法,我们这里举个Get异步请求的基本使用:

    mOkHttpClient=new OkHttpClient();
    Request.Builder requestBuilder = new Request.Builder().url("http://www.baidu.com");
    //可以省略,默认是GET请求
    requestBuilder.method("GET",null);
    Request request = requestBuilder.build();
    Call mcall= mOkHttpClient.newCall(request);
    mcall.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
        }
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if (null != response.cacheResponse()) {
                String str = response.cacheResponse().toString();
            } else {
                response.body().string();
                String str = response.networkResponse().toString();
            }
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
                }
            });
        }
    });
}

我们看到这里首先是创建了一个Request对象然后传给OkHttpClient的newCall方法,然后调用Call对象的enqueue进行异步请求,如果是同步请求就是execute()方法。

1.OkHttpClient newCall

我们看到程序前面调用Request的Builder方法,构建了一个请求的Request,然后会把这个Request对象传给OkHttpClient的newCall方法,所以我们来看下这个方法:

  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

我们看到这个方法调用了RealCall的newRealCall()方法,我们继续看这个方法干了什么:

  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

我们看到这个方法里面主要是new出了一个RealCall实例,我们看下这个构造函数:

  private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }

我们看到这个构造函数主要是赋值,同时实例化了一个拦截器RetryAndFollowUpInterceptor,然后前面程序又赋值了一个eventListener对象,这个监听主要是监听请求网络的整个过程的,系统已经有一个RecordingEventListener了,但是没有使用,如果需要你可以在OkHttpClient的Builder中设置,例如:

   client = defaultClient().newBuilder()
        .dns(singleDns)
        .eventListener(listener)
        .build();

到这里我们已经得到一个RealCall对象了,我们看前面的流程图也知道,我们接下来如果是异步的话就要调用RealCall的enqueue()方法,如果是同步请求就要调用execute()方法,我们这里就以enqueue()方法为例。

2.RealCall enqueue

我们看到我们得到一个RealCall对象,然后会根据同步还是异步请求来分别调用不同方法,这里我们看看enqueue方法:

 @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
//这个主要是获取堆栈信息并且给RetryAndFollowUpInterceptor拦截器
    captureCallStackTrace();
//调用监听的方法
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

我们看到这个函数最后调用Dispatcher中的enqueue方法。这个方法里面传入一个AsyncCall对象,我们先来看下这个对象是什么:

final class AsyncCall extends NamedRunnable {
.......
  }

这个类是一个NamedRunnable对象,NamedRunnable实现了Runnable接口,然后会在run方法里面调用execute方法,这个execute方法就在AsyncCall 中实现了。等会会执行到。那么我们先来看下Dispatcher的enqueue方法。

3.Dispatcher enqueue

这里的Dispatcher 是个线程调度池,他有以下几个作用:
1).调度线程池Disptcher实现了高并发,低阻塞的实现;
2).采用Deque作为缓存,先进先出的顺序执行;
3).任务在try/finally中调用了finished函数,控制任务队列的执行顺序,而不是采用锁,减少了编码复杂性提高性能。
知道这些概念之后我们来分析源码,来巩固这些是不是可以在代码中找到蛛丝马迹,我们跟进enqueue方法:

synchronized void enqueue(AsyncCall call) {
//判断正在执行的请求数量是否操作最大请求数且请求是否超过单个主机最大的请求数量
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//添加进正在执行的请求队列。包括正在执行的异步请求,包含已经取消但未执行完的请求
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
// 添加进准备执行的异步请求队列
      readyAsyncCalls.add(call);
    }
  }

我们看到这是如果正在执行的请求数量小于操作最大请求数且请求小于单个主机最大的请求数量则添加进正在执行的请求队列中去,否则的话则添加进准备执行的异步请求队列中去,这样减少了阻塞。我们看到如果条件符合会调用executorService()方法:

  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

这个地方使用了单例的线程池,线程池里面的几个参数这里解释下哈:

  • 0(corePoolSize):表示核心线程池的数量为 0,空闲一段时间后所有线程将全部被销毁。
  • Integer.MAX_VALUE(maximumPoolSize):最大线程数,当任务进来时可以扩充的线程最大值,相当于无限大。
  • 60(keepAliveTime): 当线程数大于corePoolSize时,多余的空闲线程的最大存活时间。
  • TimeUnit.SECONDS:存活时间的单位是秒。
  • new SynchronousQueue<Runnable>():这是个不存储元素的阻塞队列,先进先出,负责把生产者线程处理的数据直接传递给消费者线程。
  • Util.threadFactory("OkHttp Dispatcher", false):单个线程的工厂 。
    总的来说,就是有多少个并发请求就创建几个线程,当请求完毕之后,会在60s时间内回收关闭。得到这个线程池之后会调用execute执行:
    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
//调用拦截器的责任链进行网络请求
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
//调用dispatcher进行finished
        client.dispatcher().finished(this);
      }
    }

我们看到我们这里有一句非常重要的代码getResponseWithInterceptorChain,这里面就有我们一系列的拦截器了,我们可以直接跟进这个方法。

4. getResponseWithInterceptorChain

所有的很多核心功能都在这里面了,所以我们看见这个方法做了些什么:

 Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

我们看到这里面首先会添加我们自己设置的拦截器,然后会分别添加retryAndFollowUpInterceptor(负责失败重试以及重定向),BridgeInterceptor(负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应),CacheInterceptor(负责读取缓存直接返回、更新缓存),ConnectInterceptor(负责和服务器建立连接),networkInterceptors(配置 OkHttpClient 时设置的 networkInterceptors),CallServerInterceptor(负责向服务器发送请求数据、从服务器读取响应数据)。然后实例化一个RealInterceptorChain对象,调用proceed方法进行链式调用。我们跟进proceed方法:

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;
.........
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }
.......
    return response;
  }

我们看到这段代码里面实例化一个index+1的RealInterceptorChain,然后获取相应index的拦截器,调用拦截器的intercept方法。

5.retryAndFollowUpInterceptor

首先如果我们客户端没有设置拦截器,这个就是默认的第一个拦截器了,那么程序会调用这个拦截器的intercept方法:

@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();
//实例化StreamAllocation对象(Socket管理)
    streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
        call, eventListener, callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
//这里直接调用下一个拦截器来返回请求结果,然后捕获异常
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
//通过路线连接失败,请求将不会再发送
        if (!recover(e.getLastConnectException(), false, request)) {
          throw e.getLastConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
// 与服务器尝试通信失败,请求不会再发送。
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
//抛出未检查的异常,释放资源
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      if (priorResponse != null) {
//如果前面请求还没有请求结果,则我们构造一个空的请求结果body内容为空
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }
//根据请求返回的状态码,会加上验证或者重定向,或者超时的处理
      Request followUp = followUpRequest(response);

      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

      closeQuietly(response.body());

      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (followUp.body() instanceof UnrepeatableRequestBody) {
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }

      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(followUp.url()), call, eventListener, callStackTrace);
      } else if (streamAllocation.codec() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;
      priorResponse = response;
    }

我们看到我们程序首先会实例化一个StreamAllocation对象,这个对象专门负责socket管理的,这个对象里面的参数分别为连接池connectionPool,还有调用了createAddress方法,我们看下这个方法做了什么:

private Address createAddress(HttpUrl url) {
    SSLSocketFactory sslSocketFactory = null;
    HostnameVerifier hostnameVerifier = null;
    CertificatePinner certificatePinner = null;
    if (url.isHttps()) {
      sslSocketFactory = client.sslSocketFactory();
      hostnameVerifier = client.hostnameVerifier();
      certificatePinner = client.certificatePinner();
    }

    return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
        sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
        client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
  }

我们看到这个方法里面主要是实例化了Address对象,这个对象主要存储用来连接服务器所需的一些信息,包括主机,端口号,dns,以及协议,代理等信息。程序后面直接调用了下一个拦截器,所以我们先来看看下一个拦截器是干了什么,下一个拦截器是BridgeInterceptor。

6.BridgeInterceptor

我们知道这个拦截器主要是负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 。我们直接来看intercept方法:

@Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

//这里主要取出http请求报文的请求数据内容
    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
//往requestBuilder中添加请求头Content-Type内容
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
//往requestBuilder中添加请求头Content-Length内容
        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中添加请求头Host内容
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {
//往requestBuilder中添加请求头Connection内容
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
//是否支持gzip压缩,如果有则添加Accept-Encoding = gzip
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
//往requestBuilder中添加请求头Cookie内容
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") == null) {
//往requestBuilder中添加请求头User-Agent内容
      requestBuilder.header("User-Agent", Version.userAgent());
    }

//调用下一个拦截器,将请求数据传过去
    Response networkResponse = chain.proceed(requestBuilder.build());

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
//根据下面拦截器处理的请求返回数据来构建一个新的Response.Builder对象
    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);
//如果支持gzip压缩则做相应的处理
    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)));
    }
//最后返回一个Response对象
    return responseBuilder.build();
  }

我们看到这个拦截器主要的功能就是拼接request的请求头,然后传给下一个拦截器进行处理,根据下面拦截器返回的结果返回一个response对象给上面的拦截器。所以我们接下来看下一个拦截器CacheInterceptor。

7.CacheInterceptor

这个拦截器主要是负责读取缓存直接返回、更新缓存,我们同样来看intercept做了什么:

 @Override public Response intercept(Chain chain) throws IOException {
//cacheCandidate从disklurcache中获取
//request的url被md5序列化为key,进行缓存查询
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
////请求与缓存
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }
//如果缓存命中但是cacheResponse 为null则说明这个缓存非法,关闭
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
//如果网络请求request为空且返回的缓存为空,则就是出错了request不正确
    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 we don't need the network, we're done.
//如果请求为空我们就直接返回缓存
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
//调用下一个拦截器进行请求
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
//如果缓存不为空,且网络请求返回的请求码为未修改则合并header信息,存储相应信息
        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();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
//根据网络返回的response和缓存的response来构造一个Response
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the 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.
        }
      }
    }

    return response;
  }

我们看到Factory.get()就是关键的缓存策略判断了,我们进入get()方法,我们发现实际的代码在getCandidate方法中:

  private CacheStrategy getCandidate() {
      // No cached response.
//如果缓存没有命中(即null),网络请求也不需要加缓存Header了
      if (cacheResponse == null) {
 //没有缓存的网络请求则上面代码可知是直接访问
        return new CacheStrategy(request, null);
      }

      // Drop the cached response if it's missing a required handshake.
//如果缓存的TLS握手信息丢失,返回进行直接连接
      if (request.isHttps() && cacheResponse.handshake() == null) {
//也是直接访问
        return new CacheStrategy(request, null);
      }

      // If this response shouldn't have been stored, it should never be used
      // as a response source. This check should be redundant as long as the
      // persistence store is well-behaved and the rules are constant.
////检测response的状态码,Expired时间,是否有no-cache标签
      if (!isCacheable(cacheResponse, request)) {
//直接访问
        return new CacheStrategy(request, null);
      }

////如果请求报文使用了`no-cache`标签(这个只可能是开发者故意添加的)
  //或者有ETag/Since标签(也就是条件GET请求)
      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
 //直接连接,把缓存判断交给服务器
        return new CacheStrategy(request, null);
      }

//这个是新的扩展属性表示响应内容将一直不会改变,它和max-age是对缓存生命周期控制的互补性属性
      CacheControl responseCaching = cacheResponse.cacheControl();
      if (responseCaching.immutable()) {
//则直接返回缓存的信息,不进行请求
        return new CacheStrategy(null, cacheResponse);
      }

 //根据RFC协议计算
  //计算当前age的时间戳
  //now - sent + age (s)
      long ageMillis = cacheResponseAge();
//大部分情况服务器设置为max-age
      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;
//ParseHeader中的缓存控制信息
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
//设置最大过期时间,一般设置为0
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }
 //缓存在过期时间内,可以使用
  //大部分情况下是进行如下判断
  //now - sent + age + 0 < max-age + 0
      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\"");
        }
        return new CacheStrategy(null, builder.build());
      }

      // Find a condition to add to the request. If the condition is satisfied, the response body
      // will not be transmitted.
      String conditionName;
      String conditionValue;
 //缓存失效, 如果有etag等信息
  //进行发送`conditional`请求,交给服务器处理
      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 {
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }

      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }

我们可以看到,okhttp的缓存策略主要就是根据请求头和服务器的Header来控制的,自己不需要特殊的处理。当然我们前面看到了,我们可以通过设置自己的缓存拦截器来实现缓存,不过这样的话这里就不符合RFC协议标准了。我们可以通过在服务器配置恰当的缓存策略来减少http请求。我们这里通过一系列的缓存判断之后还是会讲request请求交给下一个拦截器来处理。下一个拦截器这个地方是ConnectInterceptor。

8.ConnectInterceptor

这个拦截器主要负责和服务器建立连接,socket的连接相关内容,这里也是来看intercept方法:

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

这里我们看到这里得到了一个StreamAllocation 对象,这个对象的实例化在retryAndFollowUpInterceptor拦截器中已经实例化了。我们看到这里调用了StreamAllocation对象的newStream()方法:

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

    try {
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

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

我们看到这个方法又调用了findHealthyConnection方法,我们继续跟进去看下:

 private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
      throws IOException {
    while (true) {
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          connectionRetryEnabled);

      // If this is a brand new connection, we can skip the extensive health checks.
      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          return candidate;
        }
      }

      // Do a (potentially slow) check to confirm that the pooled connection is still good. If it
      // isn't, take it out of the pool and start again.
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        noNewStreams();
        continue;
      }

      return candidate;
    }
  }

我去。。。这个里面又调用了findConnection方法,同样的,我们继续跟进去:

  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;
    Route selectedRoute = null;
    Connection releasedConnection;
    Socket toClose;
    synchronized (connectionPool) {
      if (released) throw new IllegalStateException("released");
      if (codec != null) throw new IllegalStateException("codec != null");
      if (canceled) throw new IOException("Canceled");

      // Attempt to use an already-allocated connection. We need to be careful here because our
      // already-allocated connection may have been restricted from creating new streams.
      releasedConnection = this.connection;
//如果没有新的连接则释放连接
      toClose = releaseIfNoNewStreams();
      if (this.connection != null) {
        // We had an already-allocated connection and it's good.
        result = this.connection;
        releasedConnection = null;
      }
      if (!reportedAcquired) {
        // If the connection was never reported acquired, don't report it as released!
        releasedConnection = null;
      }

      if (result == null) {
        // Attempt to get a connection from the pool.
//从缓存池里面得到一个连接
        Internal.instance.get(connectionPool, address, this, null);
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
        } else {
          selectedRoute = route;
        }
      }
    }
    closeQuietly(toClose);

    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
      // If we found an already-allocated or pooled connection, we're done.
      return result;
    }

    // If we need a route selection, make one. This is a blocking operation.
    boolean newRouteSelection = false;
//选择一个新的路由
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
      newRouteSelection = true;
      routeSelection = routeSelector.next();
    }

    synchronized (connectionPool) {
      if (canceled) throw new IOException("Canceled");
//尝试从新的路由选择器中找到合适的路由连接来从缓存池中找出有没有连接存在
      if (newRouteSelection) {
        // Now that we have a set of IP addresses, make another attempt at getting a connection from
        // the pool. This could match due to connection coalescing.
        List<Route> routes = routeSelection.getAll();
        for (int i = 0, size = routes.size(); i < size; i++) {
          Route route = routes.get(i);
          Internal.instance.get(connectionPool, address, this, route);
          if (connection != null) {
            foundPooledConnection = true;
            result = connection;
            this.route = route;
            break;
          }
        }
      }

      if (!foundPooledConnection) {
//如果连接地址未在缓存池中找到则创建一个新的连接RealConnection
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }

        // Create a connection and assign it to this allocation immediately. This makes it possible
        // for an asynchronous cancel() to interrupt the handshake we're about to do.
        route = selectedRoute;
        refusedStreamCount = 0;
        result = new RealConnection(connectionPool, selectedRoute);
        acquire(result, false);
      }
    }

    // If we found a pooled connection on the 2nd time around, we're done.
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
      return result;
    }

    // Do TCP + TLS handshakes. This is a blocking operation.
//跟服务器进行TCP+TLS 的握手
    result.connect(
        connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      reportedAcquired = true;

      // Pool the connection.
//把新的连接放进连接池中去
      Internal.instance.put(connectionPool, result);

      // If another multiplexed connection to the same address was created concurrently, then
      // release this connection and acquire that one.
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);

    eventListener.connectionAcquired(call, result);
    return result;
  }

这个类主要就是从缓存池中根据address查找是不是已经有这么一个连接了,有的话返回,然后判断是不是需要一个路由选择器,这里的next()就是选择线路,选择合适的路由和address在缓冲池中再查找合适的连接。我们就不具体跟进代码里面了,实在是有点多。我们说下这个过程:
如果Proxy为null:
1.在构造函数中设置代理为Proxy.NO_PROXY
2.如果缓存中的lastInetSocketAddress为空,就通过DNS(默认是Dns.SYSTEM,包装了jdk自带的lookup函数)查询,并保存结果,注意结果是数组,即一个域名有多个IP,这就是自动重连的来源
3.如果还没有查询到就递归调用next查询,直到查到为止
4.一切next都没有枚举到,抛出NoSuchElementException,退出(这个几乎见不到)
如果Proxy为HTTP:
1.设置socket的ip为代理地址的ip
2.设置socket的端口为代理地址的端口
3.一切next都没有枚举到,抛出NoSuchElementException,退出
到这里我们获取到我们的Connection,然后就会调用RealConnection的connect方法:

 public void connect(int connectTimeout, int readTimeout, int writeTimeout,
      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);
......
    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 {
          connectSocket(connectTimeout, readTimeout, call, eventListener);
        }
        establishProtocol(connectionSpecSelector, call, eventListener);
        eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
        break;
      } catch (IOException e) {
.......
     }

    if (route.requiresTunnel() && rawSocket == null) {
      ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: "
          + MAX_TUNNEL_ATTEMPTS);
      throw new RouteException(exception);
    }

    if (http2Connection != null) {
      synchronized (connectionPool) {
        allocationLimit = http2Connection.maxConcurrentStreams();
      }
    }
  }

这里面的connectTunnel和connectSocket方法(主要是前面那个方法是如果存在TLS,就根据SSL版本与证书进行安全握手)都会调用到connectSocket方法,这个方法我们看下:

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

    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 {
//调用Platform.get().connectSocket选择当前平台Runtime下最好的socket库进行握手,如果存在TLS,就根据SSL版本与证书进行安全握手
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    } catch (ConnectException e) {
      ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
      ce.initCause(e);
      throw ce;
    }

    // The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0
    // More details:
    // https://github.com/square/okhttp/issues/3245
    // https://android-review.googlesource.com/#/c/271775/
    try {
//利用okio库进行读写操作
      source = Okio.buffer(Okio.source(rawSocket));
      sink = Okio.buffer(Okio.sink(rawSocket));
    } catch (NullPointerException npe) {
      if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
        throw new IOException(npe);
      }
    }
  }

这里面的代码主要跟服务器的socket连接,然后在Http1Codec中,它利用 Okio 对Socket的读写操作进行封装,Okio 以后有机会再进行分析,现在让我们对它们保持一个简单地认识:它对java.io和java.nio进行了封装,让我们更便捷高效的进行 IO 操作。好了,到这里我们与服务器的连接已经完成了,当然这里面还有连接池的一些管理。我们就暂时不说明。我们接下来讲下一个拦截器CallServerInterceptor。

9.CallServerInterceptor

这个拦截器主要是负责向服务器发送请求数据、从服务器读取响应数据,我们直接来看intercept方法:

 @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();

    realChain.eventListener().requestHeadersStart(realChain.call());
//将请求的头部转化为byte格式
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        realChain.eventListener().requestBodyStart(realChain.call());
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
//将请求报文中的请求数据用okio库写到bufferedRequestBody 中去
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
//然后写入到request的body中
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
        realChain.eventListener()
            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
      } else if (!connection.isMultiplexed()) {
        // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
        // from being reused. Otherwise we're still obligated to transmit the request body to
        // leave the connection in a consistent state.
        streamAllocation.noNewStreams();
      }
    }

    httpCodec.finishRequest();

    if (responseBuilder == null) {
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(false);
    }
//根据网络请求得到Response对象
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    realChain.eventListener()
        .responseHeadersEnd(realChain.call(), response);

    int code = response.code();
.......
    return response;
  }

我们看到这里会请求网络然后返回网络的返回结果,因为这些拦截器是用了责任链的设计模式,所以,再往下传到这个拦截器完成之后,然后response的结果会往上传,然后每个拦截器再对结果进行相应的处理。如果请求的response失败,我们看到我们第一个拦截器会进行重试等操作处理,所以其实所有的核心操作都在各个拦截器中。
总结:今天讲okhttp,自我感觉有些细节讲的不是很透,时间也写的比较急促,如果有不好的地方希望提出。到这里结合那张图应该整体流程是完整的,不过有些缓存池,任务队列,复用连接池等等知识没有提的非常详细。如果有兴趣可以一起交流。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容