OKHttp源码详解

本文详细解析了OkHttp网络请求的全过程,从创建OkHttpClient对象到发起同步及异步请求,再到HTTP缓存机制,深入浅出地介绍了关键组件如Dispatcher、Interceptor、HttpCodec的作用及其工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先大概看一下整个流程图:接下来讲解的思路将根据这个流程来。

一、创建一个OkHttpClient对象:   

   OkHttpClient client = new OkHttpClient();

二、怎么不见Builder呢?

看下OkHttpClient的构造函数,其中new Builder(),这里采用了默认配置。OkHttpClient.Builder成员有很多

public OkHttpClient() {
  this(new Builder());
}


public Builder() {
  dispatcher = new Dispatcher();
  protocols = DEFAULT_PROTOCOLS;
  connectionSpecs = DEFAULT_CONNECTION_SPECS;
  proxySelector = ProxySelector.getDefault();
  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;
  connectTimeout = 10_000;
  readTimeout = 10_000;
  writeTimeout = 10_000;
}

三、发起Http请求:

3.1先看一下HTTP的同步请求:

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

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

OkHttpClient实现了Call.Factory,负责为Request创建新的Call

现在来看一下它是如何创建一个 new Call的:

/** * Prepares the {@code request} to be executed at some point in the future. */
@Override public Call newCall(Request request) {
  return new RealCall(this, request);

查看它的源码知它最后调用了RealCall();

 网络同步请求:RealCall.execute()源码:

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");  // (1)
    executed = true;
  }
  try {
    client.dispatcher().executed(this);                                 // (2)
    Response result = getResponseWithInterceptorChain();                // (3)
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    client.dispatcher().finished(this);                                 // (4)
  }
}

这里面做了4件事情

1)首先if检查这个call是否已经完全执行了,每个call只能执行一次,如果想要一个的完全一样的call,可以利用call.clone方法进行克隆。

2)利用client.dispatcher().executed(this) 来进行实际执行。这里出现了dispatcher,它也是OkHttpClient.Builder的成员之一。在同步的流程中,它的作用只是告知OkHttpClient的执行状态,比如是开始执行了(调用executed),比如执行完毕了(调用了finished)。

3)掉用getResponseWithInterceptorChain()函数来获取HTTP返回结果。

4)最后还要通知dispatcher自己已经执行完毕。

3.1.1 getResponseWithInterceptorChain()

然后先重点看一下getResponseWithInterceptorChain()的源码:

private 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(
      retryAndFollowUpInterceptor.isForWebSocket()));

  Interceptor.Chain chain = new RealInterceptorChain(
      interceptors, null, null, null, 0, originalRequest);
  return chain.proceed(originalRequest);
}

可以看到在getResponseWithInterceptorChain()函数中new List<Interceptor>集合,它把实际的网络请求、缓存、透明压缩等功能都统一起来了,每一个功能都只是一个Interceptor,它们再连接成一个Interceptor.Chain,环环相扣,最后圆满完成一次网络请求。

 getResponseWithInterceptorChain 函数我们可以看到,Interceptor.Chain 的分布依次是:


1. 在配置 OkHttpClient 时设置的 interceptors

2. 负责失败重试以及重定向的 RetryAndFollowUpInterceptor

3. 负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor

4. 负责读取缓存直接返回、更新缓存的 CacheInterceptor

5. 负责和服务器建立连接的 ConnectInterceptor

6. 配置 OkHttpClient 时设置的 networkInterceptors

7. 负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor

责任链模式在这个 Interceptor 链条中得到了很好的实践。对于把 Request 变成 Response 这件事来说,每个 Interceptor 都可能完成这件事,所以我们循着链条让每个 Interceptor 自行决定能否完成任务以及怎么完成任务(自力更生或者交给下一个 Interceptor)。这样一来,完成网络请求这件事就彻底从RealCall 类中剥离了出来,简化了各自的责任和逻辑。

 

再简单分析一下ConnectIntercetorCallServerInterceptor,看看OkHttp是怎么进行和服务器的实际通信的。

  3.1.2 ConnectInterceptor.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, doExtensiveHealthChecks);
  RealConnection connection = streamAllocation.connection();

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

它实际上就是创建一个HttpCodec对象,它是对Http协议的操作的抽象,有两个实现Http1CodecHttp2Codec,分别对应HTTP/1HTTP/2的版本实现。

Http1Codec中,它利用Okiosocket的读写操作进行封装。Okio简单理解就是:对java.iojava.nio进行了封装,让我们更便捷高效的进行IO操作。这个HttpCodec供后续步骤使用。

 

3.1.3  CallServerInterceptor.intercept():

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

  long sentRequestMillis = System.currentTimeMillis();
  httpCodec.writeRequestHeaders(request);

  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();
  }

  httpCodec.finishRequest();

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

  if (!forWebSocket || response.code() != 101) {
    response = response.newBuilder()
        .body(httpCodec.openResponseBody(response))
        .build();
  }

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

  // 省略部分检查代码

  return response;
}

该部分源码 抓住主干分为4个部分:

1)向服务器发送request header

2)如果有request body,就向服务器发送;

3)读取response header,先构造一个response对象;(这一步用到httpCodec

4)如果有response body,就在3的基础上加上body构造一个新的response对象。

 

Ok,到这里我们可以看出,客户端和服务器的实际通信,核心工作都由HttpCodec对象完成的,而HttpCodec实际上利用的是Okio,而Okio实际上还是用的socket

 

3.2 发起异步网络请求:

client.newCall(request).enqueue(new CallBack(...))

client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        System.out.println(response.body().string());
    }
});

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

// Dispatcher#enqueue
synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    runningAsyncCalls.add(call);
    executorService().execute(call);
  } else {
    readyAsyncCalls.add(call);
  }
}

enqueue的源码中也能够看到dispatch,它的作用:如果当前还能执行一个并发请求,那就立即执行;否则加入readyAsyncCalls队列中,而正在执行的请求执行完毕之后会调用promoteCalls()函数,会将队列中等待的AsyncCall提升为runningAsyncCalls,并开始执行。

这里的AsyncCallrealCall的一个内部类,它实现了Runnable,所以它可以被提交到ExecutorService上执行,而它的执行时会调用getResponseWithInterceptorChain()函数,并把结果通过responseCallback传递给上层使用者。

异步同步请求大总结:RealCall 为具体的 Call 实现,其 enqueue() 异步接口通过 Dispatcher 利用ExecutorService 实现,而最终进行网络请求时和同步 execute() 接口一致,都是通过getResponseWithInterceptorChain() 函数实现。

而getResponseWithInterceptorChain() 中利用 Interceptor 链条,分层实现缓存、透明压缩、网络IO等功能。

四、返回数据的获取:

在同步或者异步回调请求完成之后,我们就可以从response对象中获取响应数据了,包括HTTP status code , status message , response header , reponse body等。这里body部分最为特殊,因为服务器返回的数据可能非常大,所以必须通过数据流的方式来进行访问,而响应中其他部分则可以随意获取。

响应body被封装到responseBody类中,该类需要注意的两点:

1)每个body只能被消费一次,多次消费会抛出异常;

2)Body必须被关闭,否则会发送资源泄露。

在从服务器读取响应数据的 CallServerInterceptor中的源码中,我们可以看到body相关代码:

if (!forWebSocket || response.code() != 101) {
  response = response.newBuilder()
      .body(httpCodec.openResponseBody(response))
      .build();

 HttpCodec # openResponseBody 提供具体HTTP协议版本的响应body,而 HttpCodec 则是利用Okio实现具体的数据IO操作。

五、HTTP缓存

在网络请求的小节中,我们已经看到了Interceptor的布局,在负责和服务器建立连接的ConnectInterceptor之前,就是CacheInterceptor。他们执行过程就是:在建立连接之前,我们检查响应是否已经被缓存、缓存是否可用,如果可用则直接返回缓存的数据,否则就进行后面的流程,并在返回之前把网络的数据写入缓存。

缓存主要涉及HTTP协议缓存细节的实现,而具体的缓存逻辑OKHttp内置封装了一个Cache;类,它利用DiskLruCache。用磁盘上有限大小的空间进行缓存,按照LRU算法进行缓存淘汰。

如果觉得有用的话就点个赞吧,哈哈~~~~

参考博客:https://blog.piasy.com/2016/07/11/Understand-OkHttp/

 

 

 

 



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值