2020-12-25

OKHttp源码解析

OKHttp是一个处理网络请求的的开源项目,也是目前Android开发最火热的轻量级网络请求框架,所以掌握OKHttp的用法以及它的内部工作原理还是很有必要的,这篇文章就先来介绍下OKHttp的用法,然后再通过OKHttp的源码来分析它的工作流程。

基本用法

添加依赖

implementation 'com.squareup.okhttp3:okhttp:3.11.0'

同步请求(get)

// 创建OkHttpClient对象
OkHttpClient okHttpClient=new OkHttpClient();
// 创建 Request 对象
final Request request=new Request.Builder()
        .url("要请求的url")
        .get()
        .build();
// 创建Call对象
final Call call = okHttpClient.newCall(request);
new Thread(new Runnable() {
    @Override
    public void run() {
        try {
                // 发起同步请求,同步请求要在子线程中请求,否则会报错
            Response response = call.execute();
            Log.e("同步结果---- ",response.body().string()+"");
        } catch (IOException e) {
            e.printStackTrace();

        }
    }
}).start();

异步请求(get)

// 创建OkHttpClient对象
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
// 创建 Request 对象
Request request=new Request.Builder()
        .url("要请求的url")
        .get()
        .build();
// 创建Call对象
final Call call = okHttpClient.newCall(request);
// 发起异步请求
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        Log.d("okhttp_error",e.getMessage());
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Log.d("okhttp_success",response.body().string());
    }
});

异步请求(post)

// 创建OkHttpClient对象
OkHttpClient okHttpClient = new OkHttpClient();
// 创建表单数据
RequestBody requestBody = new FormBody.Builder()
        .add("username", "账号名")
        .add("password", "密码")
        .build();
// 创建 Request 对象
Request request = new Request.Builder()
        .url("要请求的url")
        .post(requestBody)
        .build();
// 创建Call对象并发起异步请求
okHttpClient.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        Log.d("okhttp", "onFailure: " + e.getMessage());
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Log.d("okhttp", "onResponse: " + response.body().string());
    }
});

源码分析

1、首先来看下创建OkHttpClient对象
// 1
OkHttpClient okHttpClient = new OkHttpClient();
// 2
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();

创建OkHttpClient实例主要有两种方式,先来看第一种通过构造方法创建实例的方式

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

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.proxyAuthenticator = builder.proxyAuthenticator;
  this.authenticator = builder.authenticator;
  this.connectionPool = builder.connectionPool;
  this.dns = builder.dns;
  this.followSslRedirects = builder.followSslRedirects;
  this.followRedirects = builder.followRedirects;
  this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
  this.connectTimeout = builder.connectTimeout;
  this.readTimeout = builder.readTimeout;
  this.writeTimeout = builder.writeTimeout;
  this.pingInterval = builder.pingInterval;

  if (interceptors.contains(null)) {
    throw new IllegalStateException("Null interceptor: " + interceptors);
  }
  if (networkInterceptors.contains(null)) {
    throw new IllegalStateException("Null network interceptor: " + networkInterceptors);
  }
}
public Builder() {
    // 分发器,主要用来执行异步请求时的策略
    dispatcher = new Dispatcher();
    // HTTP 协议
    protocols = DEFAULT_PROTOCOLS;
    // 传输层版本和连接协议
    connectionSpecs = DEFAULT_CONNECTION_SPECS;
    // 事件监听工厂
    eventListenerFactory = EventListener.factory(EventListener.NONE);
    // 代理选择器
    proxySelector = ProxySelector.getDefault();
    // cookie
    cookieJar = CookieJar.NO_COOKIES;
    // socket 工厂
    socketFactory = SocketFactory.getDefault();
    // 主机name验证
    hostnameVerifier = OkHostnameVerifier.INSTANCE;
    // 证书链
    certificatePinner = CertificatePinner.DEFAULT;
    // 代理服务器身份验证
    proxyAuthenticator = Authenticator.NONE;
    // 源服务器身份验证
    authenticator = Authenticator.NONE;
    // 连接池
    connectionPool = new ConnectionPool();
    // dns域名
    dns = Dns.SYSTEM;
    // 是否遵循 ssl 重定向
    followSslRedirects = true;
    // 是否遵循重定向
    followRedirects = true;
    // 连接失败的时候是否重试
    retryOnConnectionFailure = true;
    // 连接超时
    connectTimeout = 10_000;
    // 读超时
    readTimeout = 10_000;
    // 写超时
    writeTimeout = 10_000;
    // HTTP / 2 和 Web 套接字 ping 之间的时间间隔
    pingInterval = 0;
}

在这里只是做了一些初始化的设置,至于每个初始化参数在上面的注释中都有标识。

先来看第二种通过构造方法创建实例的方式,这里主要是通过建造者模式创建了一个OkHttpClient实例

OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
public OkHttpClient build() {
  return new OkHttpClient(this);
}

点进Builder()方法中你会发现这种方式和第一种方式的配置都是一样的。

2、创建Request对象
// get请求
Request request=new Request.Builder()
        .url("要请求的url")
        .get()
        .build();
        
// post请求        
Request request = new Request.Builder()
        .url("要请求的url")
        .post(requestBody)
        .build();        

可以看到,创建Request实例对象也是通过建造者模式创建的。

public static class Builder {
    // 请求链接
  HttpUrl url;
  // 请求方法
  String method;
  // 请求头
  Headers.Builder headers;
  // 请求体
  RequestBody body;
    // 标签
  Map<Class<?>, Object> tags = Collections.emptyMap();

  public Builder() {
    this.method = "GET";
    this.headers = new Headers.Builder();
  }
  
  public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
  }
  
  public Builder get() {
      return method("GET", null);
  }

  public Builder head() {
      return method("HEAD", null);
  }   

  public Builder post(RequestBody body) {
      return method("POST", body);
  }  
    
  public Builder url(HttpUrl url) {
      if (url == null) throw new NullPointerException("url == null");
      this.url = url;
      return this;
  }
    
  ...
}
3、创建 Call 对象
Call call = okHttpClient.newCall(request)
public interface Call extends Cloneable {

    // 请求
  Request request();
  // 同步请求
  Response execute() throws IOException;
  // 异步请求
  void enqueue(Callback responseCallback);
  // 取消请求
  void cancel();
  // 是否在请求过程中
  boolean isExecuted();
  // 是否取消
  boolean isCanceled();
  
  Call clone();
  // 工厂接口
  interface Factory {
    Call newCall(Request request);
  }
}
@Override public Call newCall(Request request) {
    // Call是一个接口,这里返回的是Call的实现类RealCall对象
  return RealCall.newRealCall(this, request, false /* for web socket */);
}
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // OkHttpClient 实例
  this.client = client;
  // 最初的Request
  this.originalRequest = originalRequest;
  // 是否支持websocket通信
  this.forWebSocket = forWebSocket;
  // 重试和重定向拦截器
  this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
  // Safely publish the Call instance to the EventListener.
  // 调用RealCall的构造方法生成实例call
  RealCall call = new RealCall(client, originalRequest, forWebSocket);
  call.eventListener = client.eventListenerFactory().create(call);
  return call;
}
4、发起同步请求
Response response = call.execute();
@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  eventListener.callStart(this);
  try {
    // 1
    client.dispatcher().executed(this);
    // 2
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } catch (IOException e) {
    eventListener.callFailed(this, e);
    throw e;
  } finally {
    // 3
    client.dispatcher().finished(this);
  }
}

先来看下注释1

client.dispatcher().executed(this);

这里调用了创建OkHttpClient时赋值的参数类型Dispatcher的executed方法,点进去看一下

 private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

synchronized void executed(RealCall call) {
  runningSyncCalls.add(call);
}

这里是将传进来的RealCall添加到一个双端队列runningSyncCalls中,这个双端队列是用来记录正在进行的同步请求的。

再来看注释2

Response result = getResponseWithInterceptorChain();

调用方法getResponseWithInterceptorChain返回的是Response,说明这个方法就是用来请求服务器并获取响应的,这个方法是OKHttp的核心,等下再来重点介绍。

最后来看注释3

client.dispatcher().finished(this);
void finished(RealCall call) {
  finished(runningSyncCalls, call, false);
}

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
  int runningCallsCount;
  Runnable idleCallback;
  synchronized (this) {
    // 4
    if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    if (promoteCalls) promoteCalls();
    runningCallsCount = runningCallsCount();
    idleCallback = this.idleCallback;
  }

  if (runningCallsCount == 0 && idleCallback != null) {
    idleCallback.run();
  }
}

重点是在注释4中将这个已完成的同步请求从双端队列中移除。

5、发起异步请求
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        Log.d("okhttp_error",e.getMessage());
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Log.d("okhttp_success",response.body().string());
    }
});
@Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  eventListener.callStart(this);
  // 1
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

这里先新建了AsyncCall对象并传入到Dispatcher的enqueue方法

public final class Dispatcher {
    // 最大并发请求数
    private int maxRequests = 64;
    // 每个主机的最大请求数
    private int maxRequestsPerHost = 5;
    // 调度程序变为空闲时调用执行
    private @Nullable Runnable idleCallback;
    // 线程池,用来执行异步请求任务的
    private @Nullable ExecutorService executorService;
    // 等待中的异步请求队列
    private final Deque<RealCall.AsyncCall> readyAsyncCalls = new ArrayDeque<>();
    // 正在处理中的异步请求队列
    private final Deque<RealCall.AsyncCall> runningAsyncCalls = new ArrayDeque<>();
    // 正在运行中的异步请求队列
    private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

    synchronized void enqueue(AsyncCall call) {
        if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
            // 当最大并发数小于64以及当前请求的主机的请求数小于5时,添加到处理中的异步请求队列
            runningAsyncCalls.add(call);
            executorService().execute(call);
        } else {
            // 添加到等待中的异步请求队列
            readyAsyncCalls.add(call);
        }
    }
}

AsyncCall是RealCall的内部类,它实现了Runnable接口,异步请求的执行过程是在这个类的execute()方法执行的

final class AsyncCall extends NamedRunnable {
  private final Callback responseCallback;

  AsyncCall(Callback responseCallback) {
    super("OkHttp %s", redactedUrl());
    this.responseCallback = responseCallback;
  }

  String host() {
    return originalRequest.url().host();
  }

  Request request() {
    return originalRequest;
  }

  RealCall get() {
    return RealCall.this;
  }

  @Override protected void execute() {
    boolean signalledCallback = false;
    try {
        // 1
      Response response = getResponseWithInterceptorChain();
      if (retryAndFollowUpInterceptor.isCanceled()) {
        signalledCallback = true;
        // 2
        responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
      } else {
        signalledCallback = true;
        // 3
        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);
        // 4
        responseCallback.onFailure(RealCall.this, e);
      }
    } finally {
        // 5
      client.dispatcher().finished(this);
    }
  }
}

先来看注释1

Response response = getResponseWithInterceptorChain();

这个请求服务器并获取响应的跟同步请求是一样的,接下来再重点介绍getResponseWithInterceptorChain()这个方法

再来看注释2、3、4

responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
responseCallback.onResponse(RealCall.this, response);
responseCallback.onFailure(RealCall.this, e);

这里就是将请求结果告知给外部的。

接着来看注释5

client.dispatcher().finished(this);
void finished(AsyncCall call) {
  finished(runningAsyncCalls, call, true);
}

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
  int runningCallsCount;
  Runnable idleCallback;
  synchronized (this) {
    // 将当前已经处理的异步请求从处理中的异步请求队列中移除
    if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    // 这个方法是用来遍历等待中的异步请求队列的,并将满足条件的任务添加到正在处理中的异步队列中并使用线程池执行
    if (promoteCalls) promoteCalls();
    runningCallsCount = runningCallsCount();
    idleCallback = this.idleCallback;
  }

  if (runningCallsCount == 0 && idleCallback != null) {
    // 当所有的异步请求任务处理完之后执行
    idleCallback.run();
  }
}

private void promoteCalls() {
      // 正在处理的并发请求数大于64,返回
    if (runningAsyncCalls.size() >= maxRequests) return; 
    // 等待中的异步请求队列为空,返回
    if (readyAsyncCalls.isEmpty()) return;

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        // 需要处理的等待请求的请求主机的请求数小于5,添加到执行队列中并使用线程池执行
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }
6、OKHttp拦截器getResponseWithInterceptorChain()方法
Response getResponseWithInterceptorChain() throws IOException {
  // 创建一个拦截器集合
  List<Interceptor> interceptors = new ArrayList<>();
  // 添加用户自定义的拦截器(1)
  interceptors.addAll(client.interceptors());
  // 添加重试与重定向拦截器(2)
  interceptors.add(retryAndFollowUpInterceptor);
  // 添加桥拦截器(3)
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  // 添加缓存拦截器(4)
  interceptors.add(new CacheInterceptor(client.internalCache()));
  // 添加连接拦截器(5)
  interceptors.add(new ConnectInterceptor(client));
  if (!forWebSocket) {
    // 添加用户自定义的网络拦截器
    interceptors.addAll(client.networkInterceptors());
  }
  // 添加服务器请求拦截器(6)
  interceptors.add(new CallServerInterceptor(forWebSocket));
    // 构建责任链(7)
  Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
      originalRequest, this, eventListener, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());
    // 依次处理责任链中的各种拦截器(8)
  return chain.proceed(originalRequest);
}

这里将多个拦截器构建成一条责任链,用到了责任链模式,每个拦截器负责特定的功能,请求会从上一个拦截器处理好之后再传给下一个拦截器处理,直到执行到责任链的最后一个拦截器,也就是服务器请求拦截器,当获取到响应之后再依次通过拦截器处理并往上传递,最后通过回调传递给外部。

来看下注释8责任链的开始执行过程

@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 {
  ...

  // 继续构建一个责任链,这时的索引是index+1,这个index一开始是0,就是我们在上面注释7中构建责任链时传入的第5个参数0
  RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
      connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
      writeTimeout);
  // 根据index从拦截器集合中获取到当前需要执行的拦截器    
  Interceptor interceptor = interceptors.get(index);
  // 执行拦截器的intercept方法,在这个方法处理完之后又会继续调用责任链的proceed方法,让下一个拦截器执行
  Response response = interceptor.intercept(next);

  ...
  return response;
}
6.1、添加用户自定义的拦截器
interceptors.addAll(client.interceptors());

如果需要对请求和响应进行一些处理的话,可以添加自定义的拦截器,比如对请求和响应数据进行统一的加解密等。

OkHttpClient.Builder builder = new OkHttpClient.Builder()
                                .connectTimeout(10 * 1000, TimeUnit.MILLISECONDS)//20秒链接超时
                .writeTimeout(10 * 1000, TimeUnit.MILLISECONDS)//写入超时20秒
                .readTimeout(10 * 1000, TimeUnit.MILLISECONDS)
                .addInterceptor(InterceptorUtil.headerInterceptor());

public List<Interceptor> interceptors() {
  return interceptors;
}

public Builder addInterceptor(Interceptor interceptor) {
  if (interceptor == null) throw new IllegalArgumentException("interceptor == null");
  interceptors.add(interceptor);
  return this;
}
6.2、重试与重定向拦截器--RetryAndFollowUpInterceptor

该拦截器主要负责失败后重试以及重定向

@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 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) {
      // 当出现RouteException时,判断是否可以恢复
      if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
        throw e.getFirstConnectException();
      }
      releaseConnection = false;
      continue;
    } catch (IOException e) {
      boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
      // 当出现IOException时,判断是否可以恢复
      if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
      releaseConnection = false;
      continue;
    } finally {
      // 如果出现了异常则释放掉资源
      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 {
        // 是否需要重定向,不需要会返回为null,主要根据响应码来判断
      followUp = followUpRequest(response, streamAllocation.route());
    } catch (IOException e) {
      streamAllocation.release();
      throw e;
    }
        // 不需要重定向,返回响应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);
      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;
  }
}
6.3、桥拦截器--BridgeInterceptor

桥拦截器就相当于一个桥梁,将用户的请求转换为可以发给服务器的请求,将服务器返回的响应转换为用户可以用的响应。

@Override 
public Response intercept(Chain chain) throws IOException {
  Request userRequest = chain.request();
  Request.Builder requestBuilder = userRequest.newBuilder();
    // 将用户的请求转换为发给服务器的请求,主要是添加一些默认的请求头
  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());
  }
  
  //执行下一个拦截器
  Response networkResponse = chain.proceed(requestBuilder.build());

  // 将服务器返回的响应转换为用户可用的响应,主要是解析服务器返回的header
  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();
}
6.4、缓存拦截器--CacheInterceptor
@Override 
public Response intercept(Chain chain) throws IOException {
    // 根据Request得到缓存
  Response cacheCandidate = cache != null
      ? cache.get(chain.request())
      : null;

  long now = System.currentTimeMillis();
    // 获取缓存策略
  CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
  // networkRequest 为 null 表示不使用网络请求
  Request networkRequest = strategy.networkRequest;
  // cacheResponse 为 null 表示不使用缓存
  Response cacheResponse = strategy.cacheResponse;

  if (cache != null) {
    cache.trackResponse(strategy);
  }
    // 有缓存,但是策略中不使用缓存,把存在的缓存资源给释放掉
  if (cacheCandidate != null && cacheResponse == null) {
    closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
  }

  // 既不使用请求也不使用缓存,则返回一个错误的响应
  if (networkRequest == null && cacheResponse == null) {
    return new Response.Builder()
        .request(chain.request())
        .protocol(Protocol.HTTP_1_1)
        .code(504)
        .message("Unsatisfiable Request (only-if-cached)")
        .body(Util.EMPTY_RESPONSE)
        .sentRequestAtMillis(-1L)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();
  }

  // 策略中不使用网络请求,说明这里是使用缓存的,直接返回缓存
  if (networkRequest == null) {
    return cacheResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .build();
  }

  Response networkResponse = null;
  try {
    // 执行下一个拦截器
    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());
    }
  }

  // 如果策略中使用缓存,并且响应码为 304,则返回缓存
  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());
    }
  }

  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;
}
6.5、连接拦截器--ConnectInterceptor
@Override 
public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  Request request = realChain.request();
  // 获取重试与重定向拦截器中创建的StreamAllocation
  StreamAllocation streamAllocation = realChain.streamAllocation();

  // We need the network to satisfy this request. Possibly for validating a conditional GET.
  boolean doExtensiveHealthChecks = !request.method().equals("GET");
  // 1、通过streamAllocation创建一个HttpCodec,HttpCodec的作用主要是进行Http请求和响应的编码与解码操作
  HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
  // 2、通过streamAllocation获取RealConnection,这个connection就是在注释1中从连接池或者重新创建的连接
  RealConnection connection = streamAllocation.connection();
    // 执行下一个拦截器
  return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

先来看注释1

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

  try {
    // 3、获取一个连接对象RealConnection
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
    // 4、通过连接对象创建HttpCodec
    HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

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

我们来看注释3是如何获取连接对象RealConnection

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
    int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
    boolean doExtensiveHealthChecks) throws IOException {
  while (true) {
    // 这个方法首先会判断当前已分配的连接是否可用,可用则进行赋值并返回,如果当前连接不可用,则会去连接池中查找符合的连接
    // 如果在连接池中找不到,则切换不同的路由再次从连接池中获取可用的连接,还是没用则会创建一个新的连接,然后进行TCP和TLS握手,
    // 最后将新创建的连接放到连接池中并返回这个创建的连接。注:OKHttp底层是通过Socket进行连接的。
    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;
  }
}

再来看注释4

HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
    StreamAllocation streamAllocation) throws SocketException {
  if (http2Connection != null) {
    return new Http2Codec(client, chain, streamAllocation, http2Connection);
  } else {
    socket.setSoTimeout(chain.readTimeoutMillis());
    source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
    sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
    // HttpCodec 对象封装了 okio 提供的输出流(BufferedSink)与输入流(BufferedSource)
    return new Http1Codec(client, streamAllocation, source, sink);
  }
}

这里主要是判断如果是 HTTP/2,则创建Http2Codec,否则创建 Http1Codec。

6.6、 服务器请求拦截器--CallServerInterceptor
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());
  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));
      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();

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

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

  realChain.eventListener()
          .responseHeadersEnd(realChain.call(), response);
    // 读取响应体
  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();
  }

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

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

HttpCodec对象封装了okio 提供的输出流(BufferedSink)与输入流(BufferedSource),服务器请求拦截器主要是通过连接拦截器创建的HttpCodec对象与服务器进行读写操作,写入请求头和请求体,读取响应头和响应体。

服务器请求拦截器是最后一个拦截器,到这里请求响应就执行完了,接着往上传递响应response,各个拦截器再进行相应的处理。接着再来说下连接池。

7、连接池ConnectionPool

连接池是用来管理HTTP和HTTP/2连接的复用的,这样可以不必创建新的连接,省去了TCP和TLS的握手,减少网络的延迟。

public final class ConnectionPool {
    // 线程池,用于清除过期的连接
    private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
            Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

    // 最大允许空闲的连接数量
    private final int maxIdleConnections;
    // 连接的存活时间
    private final long keepAliveDurationNs;
    // 清理任务,用来清理无效的连接
    private final Runnable cleanupRunnable = new Runnable() {
        @Override
        public void run() {
            while (true) {
                long waitNanos = cleanup(System.nanoTime());
                if (waitNanos == -1)
                    return;
                if (waitNanos > 0) {
                    long waitMillis = waitNanos / 1000000L;
                    waitNanos -= (waitMillis * 1000000L);
                    synchronized (okhttp3.ConnectionPool.this) {
                        try {
                            okhttp3.ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                        } catch (InterruptedException ignored) {
                        }
                    }
                }
            }
        }
    };
    // 用来记录连接的双端队列
    private final Deque<RealConnection> connections = new ArrayDeque<>();
}

连接池是在创建OkHttpClient时在它的内部静态类Builder的构造方法中创建的

connectionPool = new ConnectionPool();
public ConnectionPool() {
    // 这里设置了最大允许空闲的连接数量5个,连接的存活时间是5分钟
  this(5, 5, TimeUnit.MINUTES);
}

public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
  this.maxIdleConnections = maxIdleConnections;
  this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

  // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
  if (keepAliveDuration <= 0) {
    throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
  }
}
7.1、从连接池获取可复用的连接
@Nullable 
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
  assert (Thread.holdsLock(this));
  // 遍历连接池中保存连接对象的双端队列,取出可复用的连接
  for (RealConnection connection : connections) {
    if (connection.isEligible(address, route)) {
      streamAllocation.acquire(connection, true);
      return connection;
    }
  }
  return null;
}
7.2、往连接池中添加连接对象
void put(RealConnection connection) {
  assert (Thread.holdsLock(this));
  if (!cleanupRunning) {
    // 执行任务中
    cleanupRunning = true;
    // 1、在线程池中执行清理连接的任务
    executor.execute(cleanupRunnable);
  }
  // 将创建的连接添加到保存连接对象的双端队列中
  connections.add(connection);
}

再来看下注释1是如何清理连接对象的

private final Runnable cleanupRunnable = new Runnable() {
  @Override public void run() {
    while (true) {
      // 2、清理不满足条件的连接对象
      long waitNanos = cleanup(System.nanoTime());
      if (waitNanos == -1) return;
      if (waitNanos > 0) {
        long waitMillis = waitNanos / 1000000L;
        waitNanos -= (waitMillis * 1000000L);
        synchronized (ConnectionPool.this) {
          try {
            ConnectionPool.this.wait(waitMillis, (int) waitNanos);
          } catch (InterruptedException ignored) {
          }
        }
      }
    }
  }
};

首先这里是一个无线循环,通过阻塞来清理不满足条件的连接对象,清理逻辑主要是在注释2的cleanup方法,它会返回下次需要清理的间隔时间。

long cleanup(long now) {
  int inUseConnectionCount = 0;
  int idleConnectionCount = 0;
  RealConnection longestIdleConnection = null;
  long longestIdleDurationNs = Long.MIN_VALUE;

  // Find either a connection to evict, or the time that the next eviction is due.
  synchronized (this) {
    // 遍历连接,找到不满足条件的连接并进行清理
    for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
      RealConnection connection = i.next();

      // 查询此连接的StreamAllocation的弱引用数量,如果数量大于0,说明还被引用中
      if (pruneAndGetAllocationCount(connection, now) > 0) {
        // 说明不是空闲连接对象,记录inUseConnectionCount加1
        inUseConnectionCount++;
        continue;
      }
            // 说明是空闲连接对象,记录idleConnectionCount加1
      idleConnectionCount++;

      // 获取连接的存活时间
      long idleDurationNs = now - connection.idleAtNanos;
      if (idleDurationNs > longestIdleDurationNs) {
        longestIdleDurationNs = idleDurationNs;
        longestIdleConnection = connection;
      }
    }

    if (longestIdleDurationNs >= this.keepAliveDurationNs
        || idleConnectionCount > this.maxIdleConnections) {
      // 如果连接的存活时间大于等于5分钟,或者空闲的连接数量大于5个,则从队列中移除这个连接
      connections.remove(longestIdleConnection);
    } else if (idleConnectionCount > 0) {
      // 如果空闲数量大于0,返回此连接的剩余存活时间
      return keepAliveDurationNs - longestIdleDurationNs;
    } else if (inUseConnectionCount > 0) {
      // 如果没有空闲连接了,则返回5分钟,也就是下次清理的时间间隔是5分钟
      return keepAliveDurationNs;
    } else {
      // 没有任何连接了,则跳出循环
      cleanupRunning = false;
      return -1;
    }
  }

  closeQuietly(longestIdleConnection.socket());

  // 快速进行下一次的清理
  return 0;
}
8、用一张图来总结OKHttp的工作流程

参考:

Android 主流开源框架(三)OkHttp 源码解析

OkHttp3源码解析(整体流程)

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

推荐阅读更多精彩内容