Android源码学习-OkHttp源码浅析

介绍

OkHttp是一个用于Android和Java应用程序的HTTP客户端。相关参数和配置都是用Builder模式构建,简单使用如下。

OkHttpClient httpClient = new OkHttpClient.Builder().build();
Request request = new Request.Builder().get().url("http://www.baidu.com").build();
Call call = httpClient.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {

    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {

    }
});

上面是异步请求方式,而同步请求方式只需要替换call调用的方法即可:

call.execute();

当OkHttp发送同步请求后,就会进入阻塞状态,直到收到响应。而异步请求则是异步执行的。

源码分析

基于OkHttp3.14.0

OkHttpClient的构建

OkHttpClient是OkHttp客户端类,使用Builder模式构建,初始化OkHttpClient静态内部类Builder,并调用器build方法即可完成OkHttpClient的构建。

public Builder() {
  dispatcher = new Dispatcher();
  protocols = DEFAULT_PROTOCOLS;
  connectionSpecs = DEFAULT_CONNECTION_SPECS;
  eventListenerFactory = EventListener.factory(EventListener.NONE);
  proxySelector = ProxySelector.getDefault();
  if (proxySelector == null) {
    proxySelector = new NullProxySelector();
  }
  cookieJar = CookieJar.NO_COOKIES;
  socketFactory = SocketFactory.getDefault();
  hostnameVerifier = OkHostnameVerifier.INSTANCE;
  certificatePinner = CertificatePinner.DEFAULT;
  proxyAuthenticator = Authenticator.NONE;
  authenticator = Authenticator.NONE;
  connectionPool = new ConnectionPool();
  dns = Dns.SYSTEM;
  followSslRedirects = true;
  followRedirects = true;
  retryOnConnectionFailure = true;
  callTimeout = 0;
  connectTimeout = 10_000;
  readTimeout = 10_000;
  writeTimeout = 10_000;
  pingInterval = 0;
}

在Builder构造方法中进行相关参数的初始化赋值, 其中dispatcher分发器是OkHttp中很重要的概念,负责调度和管理异步请求队列,决定异步请求何时执行的策略。

public OkHttpClient build() {
  return new OkHttpClient(this);
}

buid方法将构建的Builder对象传入到OkHttpClient的构造方法并返回OkHttpClient对象。

OkHttpClient(Builder builder) {
  this.dispatcher = builder.dispatcher;
  this.proxy = builder.proxy;
  this.protocols = builder.protocols;
  this.connectionSpecs = builder.connectionSpecs;
  this.interceptors = Util.immutableList(builder.interceptors);
  this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
  this.eventListenerFactory = builder.eventListenerFactory;
  this.proxySelector = builder.proxySelector;
  this.cookieJar = builder.cookieJar;
  this.cache = builder.cache;
  this.internalCache = builder.internalCache;
  this.socketFactory = builder.socketFactory;

  boolean isTLS = false;
  for (ConnectionSpec spec : connectionSpecs) {
    isTLS = isTLS || spec.isTls();
  }

  if (builder.sslSocketFactory != null || !isTLS) {
    this.sslSocketFactory = builder.sslSocketFactory;
    this.certificateChainCleaner = builder.certificateChainCleaner;
  } else {
    X509TrustManager trustManager = Util.platformTrustManager();
    this.sslSocketFactory = newSslSocketFactory(trustManager);
    this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
  }

  if (sslSocketFactory != null) {
    Platform.get().configureSslSocketFactory(sslSocketFactory);
  }
  this.hostnameVerifier = builder.hostnameVerifier;
  this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
      certificateChainCleaner);
  //...
  this.connectionPool = builder.connectionPool;
  //...省略部分初始化复制代码
}

Request的构建

Request主要是OkHttp请求参数的封装,也是使用Builder模式构建,初始化Request静态内部类Builder,并调用其build方法完成Request的构建。

public Builder() {
  this.method = "GET";
  this.headers = new Headers.Builder();
}

这里默认请求方法设置为GET方法,并且初始化headers请求头参数。

Builder可以设置网络请求的方法,url等参数

public Builder get() {
  return method("GET", null);
}
public Builder url(String url) {
  if (url == null) throw new NullPointerException("url == null");

  // Silently replace web socket URLs with HTTP URLs.
  if (url.regionMatches(true, 0, "ws:", 0, 3)) {
    url = "http:" + url.substring(3);
  } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
    url = "https:" + url.substring(4);
  }

  return url(HttpUrl.get(url));
}

调用build方法,完成Request的创建。

public Request build() {
  if (url == null) throw new IllegalStateException("url == null");
  return new Request(this);
}
Request(Builder builder) {
  this.url = builder.url;
  this.method = builder.method;
  this.headers = builder.headers.build();
  this.body = builder.body;
  this.tags = Util.immutableMap(builder.tags);
}

如上,Request主要封装了url,method,headers和请求体body。

Call的创建

Call是一个接口,其实现类是RealCall。这个Call就是准备好被执行的一个请求,它可以被取消,但是不能被执行两次,因为这个对象代表的一个单独的请求响应对。

将request对象传递到OkHttpClient对象的newCall方法中进行Call对象的创建

@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;
}

该方法中直接实例化了一个RealCall对象并返回

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
  this.client = client;
  this.originalRequest = originalRequest;
  this.forWebSocket = forWebSocket;
  this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  this.timeout = new AsyncTimeout() {
    @Override protected void timedOut() {
      cancel();
    }
  };
  this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
}

retryAndFollowUpInterceptor重定向和重试拦截器是一个拦截器对象,这也是OkHttp中一个重要的概念,而OkHttp就是通过执行拦截器链中的各个拦截器完成的网络请求。

调用enqueue方法进行异步网络请求

经过前几步,客户端类、参数类,以及请求类RealCall已经完成创建,下面就是调用enqueue发起网络请求并得到回调结果了。

@Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  //...
  eventListener.callStart(this);
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

由于Call只能执行一次,如果判断executed标志位为true,则抛出异常,否则将executed置为true。

将传递进来的responseCallback对象封装成AsyncCall,而AsyncCall继承自NamedRunnable,它是一个Runnable对象。AsyncCall作为参数传递到dispatcher的enqueue方法中

void enqueue(AsyncCall call) {
  synchronized (this) {
    readyAsyncCalls.add(call);
  }
  promoteAndExecute();
}

将call对象传递到readyAsyncCalls队列,这是一个等待执行的Call队列。调用promoteAndExecute方法

private boolean promoteAndExecute() {
  assert (!Thread.holdsLock(this));

  List<AsyncCall> executableCalls = new ArrayList<>();
  boolean isRunning;
  synchronized (this) {
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall asyncCall = i.next();

      if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
      if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

      i.remove();
      executableCalls.add(asyncCall);
      runningAsyncCalls.add(asyncCall);
    }
    isRunning = runningCallsCount() > 0;
  }

  for (int i = 0, size = executableCalls.size(); i < size; i++) {
    AsyncCall asyncCall = executableCalls.get(i);
    asyncCall.executeOn(executorService());
  }

  return isRunning;

dispatcher中维护这两个队列,一个是刚看到的等待执行call队列readyAsyncCalls,还有一个是正在执行call队列runningAsyncCalls(包括正在执行以及取消了的call)。而前面enqueue方法中,我们都将call添加到了readyAsyncCalls队列中,所以该方法的作用是将符合条件的call从readyAsyncCalls中移除添加到runningAsyncCalls,并使用线程池执行这些call请求。

首先遍历readyAsyncCalls队列,判断如果当前runningAsyncCalls队列数量大于最大请求数64个,直接break跳出;如果call请求的host存在的请求数大于5,则进行下一个循环。如果上面的两个条件都过了,就将当前call从readyAsyncCalls中移除,并将其添加到executableCalls和runningAsyncCalls队列中。最后遍历executableCalls队列,调用asyncCall的executeOn方法,在线程池中执行请求

void executeOn(ExecutorService executorService) {
  assert (!Thread.holdsLock(client.dispatcher()));
  boolean success = false;
  try {
    executorService.execute(this);
    success = true;
  } catch (RejectedExecutionException e) {
    InterruptedIOException ioException = new InterruptedIOException("executor rejected");
    ioException.initCause(e);
    eventListener.callFailed(RealCall.this, ioException);
    responseCallback.onFailure(RealCall.this, ioException);
  } finally {
    if (!success) {
      client.dispatcher().finished(this); // This call is no longer running!
    }
  }
}

该方法中,主要执行了三个操作:

1.这个try块中执行当前AsyncCall

2.catch块进行了请求错误回调

3.如果未执行成功,在finally中将当前AsyncCall从runningAsyncCalls中移除

主要看一下第一个操作。AsyncCall继承了NamedRunnable对象,而在NamedRunnable的run方法中调用了execute抽象方法,所以主要执行逻辑由AsyncCall的execute完成。

 @Override protected void execute() {
    boolean signalledCallback = false;
    timeout.enter();
    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) {
      e = timeoutExit(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 {
      client.dispatcher().finished(this);
    }
  }
}

该方法执行的操作:

1.try块中调用getResponseWithInterceptorChain方法依次执行拦截器链中的拦截器获得response结果,判断retryAndFollowUpInterceptor拦截器如果是取消状态,直接进行失败回调,否则进行成功回调。

2.catch块也进行了相应的错误回调。

3.同样在finally块中,不管当前请求执行成功与否都移除了当前AsyncCall对象。

到这里就完成了OkHttp网络请求的全部操作,获取了请求成功的响应并进行了成功回调,如果出错了,也进行了相应的失败回调。

OkHttp拦截器链的执行

OkHttp网络请求的操作主要就是依次调用并执行拦截器链中的拦截器完成的。

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);
}

这里将拦截器依次添加到拦截器集合中,接下来初始化了拦截器链对象,这个拦截器链持有了所有的应用拦截器,网络拦截器,OkHttp核心以及网络调用者,最后调用了拦截器链的proceed方法

@Override public Response proceed(Request request) throws IOException {
  return proceed(request, streamAllocation, httpCodec, connection);
}

调用四个参数的proceed方法

@Override public Response proceed(Request request) throws IOException {
  return proceed(request, streamAllocation, httpCodec, connection);
}

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

  calls++;

  // If we already have a stream, confirm that the incoming request will use it.
  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");
  }

  // If we already have a stream, confirm that this is the only call to chain.proceed().
  if (this.httpCodec != null && calls > 1) {
    throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
        + " must call proceed() exactly once");
  }

  // 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");
  }

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

  if (response.body() == null) {
    throw new IllegalStateException(
        "interceptor " + interceptor + " returned a response with no body");
  }

  return response;
}

这里将index值加1,并构建了下一个拦截器链对象next,然后获取到当前index=0的拦截器对象interceptor,调用其intercept方法执行该拦截器逻辑,并将next对象传入方法中。我们看一下第一个拦截器retryAndFollowUpInterceptor的intercept方法的一个关键逻辑

public Response intercept(Chain chain) throws IOException {
  Request request = chain.request();
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  //...
  response = realChain.proceed(request, streamAllocation, null, null);
  //...
  }

这里获取前面构建的index为1的next拦截器对象,调用了它的proceed方法,又会获取到index为1的BridgeInterceptor拦截器并调用其intercept方法,所以会依次执行下一个拦截器的intercept的方法完成网络请求操作。

下面依次看一下OkHttp提供的5个拦截器的主要逻辑和作用

RetryAndFollowUpInterceptor重试和重定向拦截器

该拦截器负责当请求发生错误时进行错误重试,以及对请求进行重定向。当请求取消时,它会抛出IOException异常。

@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 streamAllocation = new StreamAllocation(client.connectionPool(),
      createAddress(request.url()), call, eventListener, callStackTrace);
  this.streamAllocation = streamAllocation;

  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(), streamAllocation, false, request)) {
        throw e.getFirstConnectException();
      }
      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, streamAllocation, 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) {
      response = response.newBuilder()
          .priorResponse(priorResponse.newBuilder()
                  .body(null)
                  .build())
          .build();
    }

    Request followUp;
    try {
      followUp = followUpRequest(response, streamAllocation.route());
    } catch (IOException e) {
      streamAllocation.release();
      throw e;
    }

    if (followUp == null) {
      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);
      this.streamAllocation = streamAllocation;
    } 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对象,然后进入while(true)循环,如果标志位canceled为true会直接抛出IOException异常。

调用拦截器链realChain的proceed方法继续执行下一个拦截器的逻辑,如果发生了RouteException异常,进入第一个catch块

// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
  throw e.getFirstConnectException();
}
releaseConnection = false;
continue;

这里判断如果这个请求是不可以恢复的话,会直接抛出异常,否则continue继续while循环执行请求。当发生IOException异常时进入第二个catch块,判断逻辑和前面相同,如果可恢复的话则继续while循环,在finally中进行了资源的释放。上面就完成了请求的重试。

调用followUpRequest方法

private Request followUpRequest(Response userResponse, Route route) throws IOException {
  if (userResponse == null) throw new IllegalStateException();
  int responseCode = userResponse.code();

  final String method = userResponse.request().method();
  switch (responseCode) {
    case HTTP_PROXY_AUTH:
      Proxy selectedProxy = route != null
          ? route.proxy()
          : client.proxy();
      if (selectedProxy.type() != Proxy.Type.HTTP) {
        throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
      }
      return client.proxyAuthenticator().authenticate(route, userResponse);

    case HTTP_UNAUTHORIZED:
      return client.authenticator().authenticate(route, userResponse);

    case HTTP_PERM_REDIRECT:
    case HTTP_TEMP_REDIRECT:
      // "If the 307 or 308 status code is received in response to a request other than GET
      // or HEAD, the user agent MUST NOT automatically redirect the request"
      if (!method.equals("GET") && !method.equals("HEAD")) {
        return null;
      }
      // fall-through
    //...省略部分代码

    case HTTP_CLIENT_TIMEOUT:
      if (!client.retryOnConnectionFailure()) {
        return null;
      }

      if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
        return null;
      }

      if (userResponse.priorResponse() != null
          && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
        // We attempted to retry and got another timeout. Give up.
        return null;
      }
      if (retryAfter(userResponse, 0) > 0) {
        return null;
      }
      return userResponse.request();
        //...省略部分代码
    default:
      return null;
  }
}

这个方法会配置请求以接收响应,它会添加请求的认证头部,对请求进行重定向,或者处理请求超时情况。如果方法中判断这几种情况都不需要处理的话,会返回null,否则会返回一个request。判断如果得到的followUp为空的话,会直接返回当前响应对象,跳出当前循环。上面就完成了请求重定向的处理,该拦截器的操作就分析完了。

BridgeInterceptor 桥拦截器

该拦截器是应用层代码和网络代码的桥梁。首先它将用户的请求构建成网络请求。然后它继续请求网络,最后它将网络响应构建成用户响应返回。下面分析intercept方法中的这几个操作

RequestBody body = userRequest.body();
if (body != null) {
  MediaType contentType = body.contentType();
  if (contentType != null) {
    requestBuilder.header("Content-Type", contentType.toString());
  }

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

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

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

// 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;
  requestBuilder.header("Accept-Encoding", "gzip");
}

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

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

上面判断用户请求中如果没有配置一些请求头,则会补全配置Content-Type、Content-Length、Host和Connection这些请求头。如果我们配置了可以接收gzip的压缩数据,我们也要负责解压传输流。判断获取到的cookies不为空的话,也将Cookie添加到header中。

在配置完请求头后,调用拦截器链的proceed方法发起请求获得响应

Response networkResponse = chain.proceed(requestBuilder.build());

得到响应,接收headers保存cookies信息,如下

HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

最后判断如果设置了可以接收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)));
}

return responseBuilder.build();

CacheInterceptor 缓存拦截器

顾名思义,这个就是处理网络请求时的缓存的拦截器。下面分析intercept方法的逻辑

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;

首先获取缓存的Response,将请求request和获取到的缓存传入到CacheStrategy的Factory中,并调用get方法获取到缓存策略对象。缓存策略的主要作用是根据传入的request和response判断是使用网络,缓存还是两者都要。取得缓存策略的过程会向请求头添加条件,例如If-Modified-Since;或者如果缓存响应数据是可能过期的话,会为其添加warnings。

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.
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();
    }

如果我们配置了不使用网络,只使用缓存。而此时从缓存策略中获取到的响应为空的话,构建一个失败的504的响应返回,否则直接返回缓存响应。

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());
  }
}

跟上面的拦截器一样,调用拦截器链的proceed方法继续执行下一个拦截器逻辑并获取响应。

// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
  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();

    // 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());
  }
}

如果从缓存策略中获取到的响应不为空,再判断网络请求返回的code为304即服务器无修改的话,我们可以直接使用缓存响应。

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;

如果上面的条件都不满足的话,那么我们就是用网络请求得到的响应,判断响应有body并且是可缓存的话就将这个响应保存到缓存中。最后判断如果这个请求的方法是不可以缓存的话,会将其从缓存中移除,这样缓存拦截器的主流程就走完了。

ConnectInterceptor 连接拦截器

连接拦截器打开一个到目标服务器的连接,并且调用下一个拦截器。

@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对象,调用newStream方法

public HttpCodec newStream(
    OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    //...省略部分代码
  try {
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, pingIntervalMillis, 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, int pingIntervalMillis, boolean connectionRetryEnabled,
    boolean doExtensiveHealthChecks) throws IOException {
  while (true) {
    RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
        pingIntervalMillis, 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;
  }
}

这个方法会一直执行while循环直接到找个一个健康的连接。调用findConnection方法,该方法中的方法里逻辑较多,分步来看

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;
}

首先尝试使用已经分配过的connection连接,如果connection不为空,即我们可以使用它。

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;
}

如果connection为空,会试图从连接池中获取,如果connection不为空将其赋值给result。到这里如果我们找到了已经分配的connection或者是连接池中的connection,就将connection返回。

//...
result = new RealConnection(connectionPool, selectedRoute);
//...

如果上面都没找到可用的connection,这里实例化了一个新的connection赋值给result。

// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
    connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());

调用了result的connect方法建立了连接,其内部是建立的socket连接,内部具体细节暂时不深究。最后调用connection的newCodec方法获取了HttpCodec对象,HttpCodec对象封装了okio的输入和输出流对象。

将request和streamAllocation以及这步获得的connection、httpCodec对象传入到拦截器链的proceed方法调用下一个拦截器。

CallServerInterceptor 请求服务拦截器

这是拦截器链中的最后一个拦截器,它负责向服务器发送网络请求。

看一下它interceptor方法具体发送网络请求的流程:

httpCodec.writeRequestHeaders(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));
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

    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();

这里判断如果请求头中有Expect: 100-continue信息,则请求时会先不发送请求体信息,直到接收到服务器的100-continue应答后才会将数据post给服务器。

Expect: 100-continue解释:客户端先发送了一个请求,这个请求的header中包含了一个属性expect: 100-continue。这种情况一般出现于上传大容量body或者是需要验证的时候。这时服务器会读取请求的header并返回一个100 continue的响应,如果服务器可以提供这项服务的话。客户端再将http请求发送回去。然后服务器会读取请求的body并且在成功后返回200状态码。这样做可以避免带宽浪费。

int code = response.code();
if (code == 100) {
  // server sent a 100-continue even though we did not request one.
  // try again to read the actual response
  responseBuilder = httpCodec.readResponseHeaders(false);

  response = responseBuilder
          .request(request)
          .handshake(streamAllocation.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();

  code = response.code();
}

如果我们没有指定Expect: 100-continue请求,服务器也返回了100-continue响应的话,会再次尝试获取的真实的响应。

if (forWebSocket && code == 101) {
  // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
  response = response.newBuilder()
      .body(Util.EMPTY_RESPONSE)
      .build();
} else {
  response = response.newBuilder()
      .body(httpCodec.openResponseBody(response))
      .build();
}

如果收到了101响应,即服务端理解了客户端的请求,并通知客户端采取不同的协议,我们会重新构建response以确保拦截器收到是非空的响应,否则直接拿到响应。

if ("close".equalsIgnoreCase(response.request().header("Connection"))
    || "close".equalsIgnoreCase(response.header("Connection"))) {
  streamAllocation.noNewStreams();
}

当请求的头部字段或者响应的头部字段Connection为close时,则会断开连接,并且会禁止connection产生新的流。

if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
  throw new ProtocolException(
      "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;

这里状态码表示服务器接收的请求已成功处理,但在请求报文中不含实体的主体部分。另外也不允许返回任何实体的主体。

这里判断如果响应码是204或者205,但是响应报文包含实体部分时就会抛出异常,否则将得到的response返回。

最后随着OkHttp拦截器链中拦截器的依次执行,OkHttp网络请求就结束了。

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

推荐阅读更多精彩内容