【源码解析】OkHttp的工作原理

2,188 阅读10分钟

引言

OkHttp作为优秀的网络请求框架,已经得到了广大Android开发者的认可。对于它的使用方法,大家也是非常的熟悉。例如同步同步请求、异步请求等,都可以使用很简洁的逻辑来实现。由于OkHttp已经封装了繁琐复杂的请求逻辑,开发者只需要使用其提供的API就能轻松的实现网络请求,这使得开发者能将更多的精力放到业务开发上,提高了工作效率。

但是,作为一位有追求的Android开发者,不能一味的衣来伸手饭来张口。虽然不至于要自己动手,丰衣足食,但是我们也要做到知其然更知其所以然,从优秀的基础框架中学习其设计思想、架构思想,这对我们的成长也是有非常大的帮助的。

下面我们就以OkHttp为例,从源码的角度对其整体流程进行分析,以便能从中学习到其设计思路与工作原理。

整体架构

下面是OkHttp发起请求与数据响应的流程图(参考拆轮子系列:拆 OkHttp这篇文章画的)。

整体来说,是OkHttpClient通过newCall方法,进而触发RealCall的execute(同步)、enquene(异步)方法,经过一系列的interceptors(拦截器),最后经过IO操作发起请求得到Response(响应数据)。

下面针对上述OkHttp的整体工作流程,从源码的角度分析其中的原理,我们首先从同步请求说起。

同步请求

OkHttp的同步请求示例如下所示。

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url(url)
    .build();

Response response = client.newCall(request).execute();
return response.body().string();

OkHttpClient有个内部类Builder,它的build方法可以生成一个OkHttpClient实例。但OkHttpClient提供了一个构造函数,让我们可以使用默认的配置来创建OkHttpClient实例。

OkHttpClient实例的newCall方法接收一个Request对象,该对象由Request的内部类Builder构造出来。

public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false);
}

newCall方法中通过RealCall又调用了newRealCall方法,并返回RealCall对象。也就是说,实际上执行的是RealCallexecute方法。

public Response execute() throws IOException {
    synchronized (this) { // 1
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    timeout.enter();
    eventListener.callStart(this);
    try {
        client.dispatcher().executed(this);
        Response result = getResponseWithInterceptorChain(); // 2
        if (result == null) throw new IOException("Canceled");
        return result;
    } catch (IOException e) {
        e = timeoutExit(e);
        eventListener.callFailed(this, e);
        throw e;
    } finally {
        client.dispatcher().finished(this);
    }
}

1、每一个RealCall对象的execute方法只能执行一次,多次执行会抛出IllegalStateException异常。

2、通过执行getResponseWithInterceptorChain方法同步返回了Response对象。从上面的整体架构可知,getResponseWithInterceptorChain方法是OkHttp发起网络请求的重点部分,我们接着往下面看。

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

getResponseWithInterceptorChain方法中的往List中加了很多拦截器,最后通过构造了RealInterceptorChain对象,并执行它的proceed方法返回一个Response对象。该方法的工作流程如下图所示。

这就是所谓的责任链模式,每一个节点负责自己的一部分工作,最后组装成一个完整的请求对象发送网络请求并返回Response。对于其中的每一个节点(拦截器),我们通过源码进一步分析其中的细节。

RealInterceptorChain

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
    RealConnection connection) throws IOException {
  ...省略
  
  //生成下一个节点
  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);

  ...省略

  return response;
}

RealInterceptorChainproceed方法主要做了两个操作:

1、生成了下一个节点对象next,类型为RealInterceptorChain

2、调用了当前拦截器的intercept方法,并将next对象传递给该拦截器。从上述流程图中,我们认定当前拦截器是RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor

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) { //这里死循环的意思是会一直重试,直到遇到return或者抛出异常后才会跳出
    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) {
      ...
      continue;
    } catch (IOException e) {
      ...
      continue;
    } finally {
      ...
    }

    ...

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

    if (followUp == null) { //不需要重试,直接返回Response对象
      streamAllocation.release();
      return response;
    }

    closeQuietly(response.body());

    if (++followUpCount > MAX_FOLLOW_UPS) { //重试次数加1,重试次数大于最大次数,释放资源
      streamAllocation.release();
      throw new ProtocolException("Too many follow-up requests: " + followUpCount);
    }

    //继续重试,直到重试次数大于最大重试次数
    request = followUp;
    priorResponse = response;
  }
}

RetryAndFollowUpInterceptor,从它的名字也能看出来,其主要目的是为失败重试和重定向而工作的。所以它的intercept方法主要利用了while循环进行多次的请求重试,只有当重试次数大于最大重试次数时才会跳出while循环。

BridgeInterceptor

RetryAndFollowUpInterceptorintercept方法将沿着责任链,从而执行到了BridgeInterceptorintercept方法。

public Response intercept(Chain chain) throws IOException {
  Request userRequest = chain.request();
  Request.Builder requestBuilder = userRequest.newBuilder(); //构造了一个新的Request.Builder对象

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

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

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

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

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

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

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

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

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

  if (transparentGzip
      && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
      && HttpHeaders.hasBody(networkResponse)) {
    GzipSource responseBody = new GzipSource(networkResponse.body().source());
    Headers strippedHeaders = networkResponse.headers().newBuilder()
        .removeAll("Content-Encoding")
        .removeAll("Content-Length")
        .build();
    responseBuilder.headers(strippedHeaders);
    String contentType = networkResponse.header("Content-Type");
    //将网络返回的数据构进一步的封装并返回
    responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
  }

  return responseBuilder.build();
}

BridgeInterceptor的拦截方法要做的事情并不多,主要目的是判断网络请求Request对象header对象的字段为空时,构造一个默认对象,而且在网络数据返回后,将返回的Response对象进一步的封装并返回。

CacheInterceptor

BridgeInterceptorintercept方法又将网络请求继续分发,它的下一个拦截器则是CacheInterceptor。从CacheInterceptor的名字与其注释可知,该拦截器的主要功能是获取缓存的Response以及将网络请求返回Response存储到缓存中。

public Response intercept(Chain chain) throws IOException {
  Response cacheCandidate = cache != null
      ? cache.get(chain.request())
      : null;

  ...省略

  //没有网络,则使用缓存
  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());
    }
  }

  // 有缓存,如果返回的Response和缓存比对后没有改变,则返回缓存
  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();

      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) { //缓存改变了,则重新写入新的Response到缓存
    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;
}

从上面的interceptor方法中可以看出,该方法主要做了以下事情。

1、判断是否需要返回缓存。(一般无网络时会返回缓存)

2、有网络的情况下,Request请求正常发送,判断返回的Response内容是否有更新。没有更新,则返回缓存内容;有更新,则返回新的Response,并将新的Response内容写入到缓存中。

ConnectInterceptor

CacheInterceptor拦截器的下一个责任链节点是ConnectInterceptor拦截器。

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

ConnectInterceptor拦截器的intercept方法主要负责建立连接,也就是创建了HttpCodec对象。HttpCodec是一个接口类,有两个实现类,分别为Http1CodecHttp2CodecHttp1Codec使用的是Http1.0版本的协议发送网络请求,而Http2Codec使用的是Http2.0版本的协议发送网络请求。从HttpCodec接口方法和其实现逻辑来看,其中主要封装了Java的IO操作,通过stream字节流参与网络请求的发送与接收过程。

CallServerInterceptor

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());
  httpCodec.writeRequestHeaders(request);
  realChain.eventListener().requestHeadersEnd(realChain.call(), request);

  Response.Builder responseBuilder = null;
  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
      httpCodec.flushRequest();
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(true);
    }

    if (responseBuilder == null) {
      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()) {
      streamAllocation.noNewStreams();
    }
  }

  httpCodec.finishRequest();

  if (responseBuilder == null) {
    realChain.eventListener().responseHeadersStart(realChain.call());
    responseBuilder = httpCodec.readResponseHeaders(false);
  }

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

  ...省略
  return response;
}

CallServerInterceptor作为最后一个拦截器,其主要利用了HttpCodecreadResponseHeaders方法获取Response数据,并将Response返回。

至此我们分析完了OkHttp中关于同步强求的整体流程。其中特别重要的是OkHttp中的拦截器分层原理,也就是所谓的责任链设计模式。OkHttp的请求会将经过拦截器一层层的分发,直到有拦截器将Response进行返回。而返回的Response也会传递到之前的每一个拦截器,每一个拦截器对该Response进行加工封装,最后形成一个统一的Response对象返回。

异步请求

OkHttp的异步请求示例如下所示。

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url(url)
    .build();

okHttpClient.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        //请求失败的回调
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        //请求成功的回调
    }
});

异步请求调用了okHttpClient.newCall方法,从上面的同步请求分析可以知道,okHttpClient.newCall返回一个RealCall对象,也就是说异步请求其实调用的是RealCall的enqueue方法。

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

client.dispatcher返回一个Dispatcher对象。该对象的功能如其名字一样,它会将请求进行管理并分发,其中的enqueue方法如下所示。

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

enqueue方法将AsyncCall对象加入了readyAsyncCalls队列,然后执行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;
}

promoteAndExecute方法执行时,会比较readyAsyncCalls队列中的请求对象个数是否大于maxRequests的值,如果readyAsyncCalls队列的请求对象个数小于maxRequests,则将这些请求对象加入到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!
    }
  }
}

executeOn方法执行的逻辑是:通过ExecutorService(线程池)执行每一个AsyncCall请求对象,所以相应的AsyncCall对象的run方法会被执行,而run方法调用了execute方法。

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

execute方法中,通过getResponseWithInterceptorChain方法返回Response对象。这里的getResponseWithInterceptorChain方法执行过程在同步请求时已经分析完了,这里不再重复说明。

至此,异步请求流程也已经分析完毕。和同步请求流程相对比,异步请求流程比同步流程多了一步也就是将请求对象进行分发并放到线程池中去执行,至于拦截器分层、发起网络请求、数据返回流程等都是一样的。

总结

OkHttp作为一个优秀的网络请求库,其主要运用责任链模式、分层思想、单一职责思想等设计模式与思想,通过每一层拦截器对请求Request和返回Response进行封装。隐藏了繁琐复杂的网络请求流程,提供简洁的API供开发者调用。通过源码解析,我们了解并学习了其中的设计原理与整体流程,做到了知其然也知其所以然。