OkHttp源码与架构(二)

124 阅读7分钟

从使用方法来看OkHttp源码:

从上一部分,我们知道了OkHttp的使用方法。使用OkHttp很简单,只需要:

  1. 引入依赖
  2. 创建一个OkHttp客户端
  3. 创建一个request请求
  4. 使用request请求和客户端,创建一个call对象,并执行
  5. 获取数据 使用很简单,不知道源码中OkHttp如何实现的网络访问、流程是什么样的。这让人很好奇。接下来看看在开发者使用OkHttp和服务器交互的时候,源码做了那些事情吧。

同步请求

从创建客户端开始看源码的原理与过程:

1. 创建客户端

1.1 方法一:使用默认的OkHttp客户端

OkHttpClient okHttpClient = new OkHttpClient();

1.2 方法二:使用builder创建客户端

OkHttpClient client=new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();

其实这两中方法在源码中是一样的,我好像说了一句废话:)。

OkHttpClient.java

public OkHttpClient() {
// 先创建默认builder,然后调用OkHttpCLient(Builder builder)创建一个OKHttpClient
  this(new Builder());
}

public Builder() {
      // 新建一个Dispatcher对象
      dispatcher = new Dispatcher();
      // 使用默认接口:
      protocols = DEFAULT_PROTOCOLS; 
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      // 使用默认proxy
      proxySelector = ProxySelector.getDefault();
      // 默认不使用cookies
      cookieJar = CookieJar.NO_COOKIES;
      // 使用默认socket
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      // 新建连接池
      connectionPool = new ConnectionPool();
      // 初始化DNS
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      // 失败重试
      retryOnConnectionFailure = true;
      // 连接超时、读取超时、写入超时等时间设置
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
    }
    
public OkHttpClient build() {
      return new OkHttpClient(this);
    }
    
private 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.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 = systemDefaultTrustManager();
      this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
    }

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

在方法一中,使用默认的OkHttpCLient,其实是先创建了一个默认的buider,然后把这个默认的builder当做参数,传入OkHttpClient(Builder builder),最终创建一个OkHttpClient对象。

在方法二中,也是先创建了一个builder对象,但是这个builder是可以设置属性的,例如时间等各种属性。接着和方法一在源码中的表现一样,创建一个OKHttpClient对象。

对比这两种方法的源码不难发现,其实两种方法本质上是没有区别的。都是需要创建一个builder,然后使用这个builder创建OKHttpClient。唯一的不同就是在创建builder的时候,方法二可以修改许多builder的属性,方法一则是使用默认的builder的属性,这些属性包含接口、分发器、连接池、时间、代理、是否使用cookies等。

接下来看看创建request对象

2. 创建request对象

创建request对象的方法如下:

// get请求
Request request = new Request.Builder()
        .url("http://www.baidu.com")
        .build();

接下来看看源码做了什么

Request.java

public Builder() {
  //默认请求方法是get
  this.method = "GET"; 
  // 创建一个header对象,用于存储kttp请求头
  this.headers = new Headers.Builder(); 
}

/**
 * 添加请求的url
*/
public Builder url(String url) {
  if (url == null) throw new NullPointerException("url == null");
  // Silently replace websocket 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);
  }
  HttpUrl parsed = HttpUrl.parse(url);
  if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
  return url(parsed);
}

public Request build() {
  if (url == null) throw new IllegalStateException("url == null");
  return new Request(this);
}

private Request(Builder builder) {
  this.url = builder.url;
  this.method = builder.method;
  this.headers = builder.headers.build();
  this.body = builder.body;
  this.tag = builder.tag != null ? builder.tag : this;
}

创建request和创建OkHttpClient对象基本一样,先创建一个buider,builder中设置默认方法为GET,然后设置整个request的URL,URL作为字符串传入之后被转换为HttpUrl对象,然后在build()方法执行的时候,将builder传入,用builder创建一个Request对象。

3. 创建call对象

okHttpClient.newCall(request).execute();

创建call对象和执行execute()方法是OkHttp中比较复杂的部分。

OkHttpClient.java

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

可以看到,在开发者调用OkHttpClient创建对象的时候,其实是使用传来的request和初始化的OKHttpClient对象创建了一个RealCall。

RealCall.java

protected RealCall(OkHttpClient client, Request originalRequest) {
  this.client = client;
  this.originalRequest = originalRequest;
  // 创建默认的拦截器:重定向拦截器
  this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client);
}

在RealCall中,新建一个RealCall对象,其实除了使用传来的两个重要参数之外,设置了一个默认的拦截器:重定向拦截器。

创建完RealCall之后,调用execute()方法,请求服务器响应。RealCall实现Call接口,实现Call中定义的execute()方法。

RealCall.java

@Override public Response execute() throws IOException {
// synchronized 轻量级锁
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  try {
    // 1.交给分发器
    client.dispatcher().executed(this); 
    // 2.从拦截器链获取处理之后的response
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    // 3.分发器释放资源
    client.dispatcher().finished(this);
  }
}

从代码里可以看到,这里有三个重要的方法,

  1. client.dispatcher().executed(this);
  2. getResponseWithInterceptorChain();
  3. client.dispatcher().finished(this); 接下来分别看看这三个方法在源码中做了什么

3.1 交给分发器

RealCall.java

/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
  runningSyncCalls.add(call);
}

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

原来 client.dispatcher().executed(this);这个方法就是把传入的call添加到runningSyncCalls中。仔细看看runningSyncCalls,原来是一个双端队列。也就是说其实这一步就是把call添加到双端队列runningSyncCalls中。但是为什么需要一个双端队列呢?看起来是依次将call加入队尾,每次取出队头的call处理即可。也就是说这里只需要一个先进先出(FIFO)的数据结构,在Java中实现Deque接口的数据结构理论上都可以,但是因为这个队列有频繁的换入换出,为了性能考虑,不能使用基于链表类的结构(LinkedList)。

在看代码的时候还发现了其他两个队列:

RealCall.java

  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

从代码的注释中不难看出来这三个队列的功能:

  1. runningSyncCalls:正在进行的同步队列,用于存储正在运行的同步call
  2. readyAsyncCalls:等待执行的异步任务队列,用于存放处于等待中的异步call。
  3. runningAsyncCalls:正在运行的异步队列,用于存储正在运行的异步call

3.2 拦截器处理并返回response

接下来看看在源码执行getResponseWithInterceptorChain();时做了什么

RealCall.java

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

在这个方法中依次添加了五个OkHttp内置的拦截器,这五个拦截器分别是:

  1. RetryAndFollowUpInterceptor 重试和重定向拦截器
  2. BridgeInterceptor 桥接拦截器
  3. CacheInterceptor 缓存拦截器
  4. ConnectInterceptor 连接拦截器
  5. CallServerInterceptor 请求服务拦截器

在创建完这五个拦截器之后,使用RealInterceptorChain类来初始化拦截器链chain,然后使用这个初始化的拦截器链chain调用chain.preoceed(originalRequest)来处理一开始传来的请求,返回response。新建RealInterceptorChain对象的时候,传入了五个参数:存储拦截器的列表interceptors、StreamAllocation、HttpStream、connection、拦截器开始的索引index、最初的请求。

其实获取response对象是通过RealInterceptorChain中的proceed()实现的。接下来看看这个方法。

RealInterceptorChain.java

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

public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream,
    Connection connection) throws IOException {
 ......
  // Call the next interceptor in the chain.
  RealInterceptorChain next = new RealInterceptorChain(
      interceptors, streamAllocation, httpStream, connection, index + 1, request);
  Interceptor interceptor = interceptors.get(index);
  Response response = interceptor.intercept(next);
  // Confirm that the next interceptor made its required call to chain.proceed().
  if (httpStream != null && index + 1 < interceptors.size() && next.calls != 1) {
    throw new IllegalStateException("network interceptor " + interceptor
        + " must call proceed() exactly once");
  }
 
  return response;
}

proceed()方法中,新建一个RealInterceptorChain对象next,指向索引的下一个位置,然后根据index从拦截器list中获取对应的拦截器,调用拦截器的方法处理next。其实在这里本质上就是遍历interceptors中的所有拦截器,依次使用各个拦截器的intercept()方法处理,最终返回一个response。 大致的处理流程如下:

image.png

在后续分析拦截器时,会有详细说明。

3.3 释放分发器资源

在处理完请求并返回response之后,diapatcher执行finished()释放资源:client.dispatcher().finished(this); 看看finished()中具体做了那些事吧。

Diapatcher.java

/**Used by {@code Call#execute} to signal completion. */
  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) {
      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();
    }
  }

对于同步请求,其实就是将call从runningSyncCalls删除,然后重新统计runningSyncCalls中的call数量,如果数量为零并且idleCallback不为空,就执行idleCallback.run();释放资源。

异步请求

异步请求的使用方法:

/ 异步请求
// 1.创建OkHttpClient对象 
OkHttpClient asyncOkHttpClient = new OkHttpClient.Builder().build();
// 2.创建request对象
Request asyncRequest = new Request.Builder()
        .url("http://www.baidu.com")
        .build();
 // 3.创建call对象       
Call asyncCall = asyncOkHttpClient.newCall(request);
// 4.入队,等待回调
asyncCall.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        
    }

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

    }
});

从使用方法来看,前三步和同步请求没有区别,第四步的时候将异步请求入队,等待http响应之后的回调。所以异步请求就从入队之后来看源码。

4. 入队,等待回调

RealCall.java

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

使用传入的Callback回调创建一个新的AsyncCall对象,然后将这个对象入队到OkHttpClient的dispatcher的队列中。接下来看看dispatcher对象如何处理

Dispatcher.java

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

这里是满足if()中的条件后,就加入到runningAsyncCalls队列中,然后将这个call放入线程池。否则就添加到readyAsyncCalls中。 详细看看if()中的变量及判断条件

Dispatcher.java

  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  
  /** Returns the number of running calls that share a host with {@code call}. */
  private int runningCallsForHost(AsyncCall call) {
    int result = 0;
    for (AsyncCall c : runningAsyncCalls) {
      if (c.host().equals(call.host())) result++;
    }
    return result;
  }

这样看if()中的判断条件就很清楚了。条件为:正在运行的异步请求队列的大小<最大请求数(64)并且和这个请求共享主机的请求数<每个主机的最大请求数(5)。当满足这个条件的时候,就将call加入到正在运行的异步队列中,并且交给线程池处理,否则就放入到等待异步请求队列中存储。 但是执行到这里似乎并没有获得一个response对象,也没有向同步请求那样通过调用getResponseWithInterceptorChain()获取response对象。在异步请求中是将call放入线程池中,那么这个异步请求应该是一个Runnable对象才对。

再看看这个AsyncCall

RealCall.java

final class AsyncCall extends NamedRunnable {
  private final Callback responseCallback;
  ...
  @Override protected void execute() {
    boolean signalledCallback = false;
    try {
      Response response = getResponseWithInterceptorChain();
      if (retryAndFollowUpInterceptor.isCanceled()) {
        signalledCallback = true;
        responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
      } else {
        signalledCallback = true;
        responseCallback.onResponse(RealCall.this, response);
      }
    } catch (IOException e) {
      if (signalledCallback) {
        // Do not signal the callback twice!
        Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
      } else {
        responseCallback.onFailure(RealCall.this, e);
      }
    } finally {
      client.dispatcher().finished(this);
    }
  }
}

AsyncCall继承了一个奇怪的NamedRunnable,那就先看看这个类吧

NamedRunnable.java

/**
 * Runnable implementation which always sets its thread name.
 */
public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

果然实现了Runnable,定义了一个抽象方法execute()

接着返回AsyncCall看看execute()方法。

RealCall.java

@Override protected void execute() {
    boolean signalledCallback = false;
    try {
      // 获取response
      Response response = getResponseWithInterceptorChain();
      if (retryAndFollowUpInterceptor.isCanceled()) {
        signalledCallback = true;
        responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
      } else {
        signalledCallback = true;
        responseCallback.onResponse(RealCall.this, response);
      }
    } catch (IOException e) {
      if (signalledCallback) {
        // Do not signal the callback twice!
        Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
      } else {
        responseCallback.onFailure(RealCall.this, e);
      }
    } finally {
      // 释放资源
      client.dispatcher().finished(this);
    }
  }

在AsyncCall中,继承了父类NamedRunnable,也实现了父类的抽象方法execute()。在这个方法中,通过getResponseWithInterceptorChain()将call交给拦截器处理,最终获得response。看来无论是同步还是异步,都是通过getResponseWithInterceptorChain()获取response的。接下来根据不同的情况,判断是回调成功还是失败,返回给上层。最后释放资源。同步队列释放资源的方法已经看过了,接着看看异步释放资源是否有所不同。

Dispatcher.java

/** Used by {@code AsyncCall#run} to signal completion. */
  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();
    }
  }
  

在异步队列请求释放资源的时候,promoteCallstrue,除了从请求队列中移除call之后,还需要执行promoteCalls()方法。只有一部队列才执行这个方法,看来是专门为异步队列准备的。

Dispatcher.java

private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

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

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

在这个方法中,进行了多种特判:

  • 运行中的异步队列的大小>最大请求数(64),返回
  • 等待的异步对列为空,返回
  • 不满足以上条件,进入循环
  • 在循环中每次从等待队列中取出一个,如果满足正在运行的队列的共享主机数是否小于每台主机的最大请求数(5),就从等待队列中删除这个call,将这个call放入正在运行的异步对列队尾,然后放入线程池。接着重新判断正在运行的请求队列大小是否超过最大请求数(64),如果满足条件则结束循环。

这样分析结束,原来这个方法是调度等待请求队列和运行请求队列的,在合适的时候将等待请求队列中的call添加到正在运行的请求队列中去。

回到finished()方法中,看看接下来执行的runningCallsCount()方法。

Dispatcher.java

public synchronized int runningCallsCount() {
  return runningAsyncCalls.size() + runningSyncCalls.size();
}

原来就是统计正在运行的请求的数量,包括正在运行的异步请求和同步请求。

原来在finished()中,需要判断是否有正在运行的线程,只有均为空的时候,才会调用空闲线程释放资源。

到此位置,通过使用方法对源码的阅读就结束了,但是还有很多关键类以及OkHttp整体架构还没看。接下来的章节中来看看一下关键类和架构吧。