OKHttp源码解析 2:同步请求与异步请求流程和源码分析

前言

前面一章已经对OKHttp的同步和异步请求的基本用法进行了讲解,总的来说,都是分为四个步骤。前面三步都是一样的,只有第四步有调用方法的区分,同步调用的是 Call 的 execute() 方法,异步调用的是 Call 的 enqueue() 方法。现在我们就根据这四步的调用流程,一步一步深入到源码,看一下 OKHttp 都做了一些什么。

首先说明:笔者现在用的 OKHttp 版本为 com.squareup.okhttp3:okhttp:3.10.0,跟踪源码的过程中,我会尽量把每一个步骤的源码都贴出来,方便大家可以一边看文章一边读源码,当然也建议大家能够打开Android Studio,跟着我一起跟踪源码,因为这样你会对源码有更全面深入的了解。好了,话不多说,直接进入正题。

OKHttpClient创建时,主要都做了什么?

首先,我们从 OKHttpClient 的创建入手,

//创建OkHttpClient,这里只简单设置了超时时间为10秒
OkHttpClient okHttpClient = new OkHttpClient.Builder().readTimeout(10, TimeUnit.SECONDS).build();

这里可以看到,OKHttpClient 是通过 Builder 模式创建的,现在我们进入到 Builder() 方法,源码如下:

public Builder() {
    //OKHttp核心:分发器类
    dispatcher = new Dispatcher();
    protocols = DEFAULT_PROTOCOLS;
    connectionSpecs = DEFAULT_CONNECTION_SPECS;
    eventListenerFactory = EventListener.factory(EventListener.NONE);
    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;
    pingInterval = 0;
}

Builder构造方法中,最重要的两个类是 Dispatcher 和 ConnectionPool,源码中我已经分别加了注释。

其中 Dispatcher 是 OKHttp 的核心类:分发器类,由它决定异步请求是直接加入到请求队列中,还是加入到等待队列中。当然,同步请求也是由它控制,只是没有异步请求这么复杂。在后面的章节中,我会重点分析这个类,这里只做简要的功能说明。

另外一个就是 ConnectionPool,它是 OKHttp 的连接池,也是 OKHttp 中一个很重要的概念。它主要起到复用http连接、管理http连接的作用,后面章节也会对它进行重点讲解。

除了这两个重要的参数,Builder 构造方法中还有各种其他的参数,它们都是在这个构造方法中进行初始化,然后传递 Builder 对象到 OKHttpClient 的构造方法中,完成 OKHttpClient 的各种参数的初始化。

//通过Builder()构造方法对各参数进行初始化之后,调用build()方法,传递Builder对象
//(this关键字代表的就是Builder对象)到OKHttpClient中,完成OKHttpClient对象的创建
public OkHttpClient build() {
    return new OkHttpClient(this);
}

这就是Builder构建模式,当有很多参数需要初始化时,我们可以使用这种模式来创建对象,很多大型开源框架中都有用到,在我们日常的业务开发中,也可以使用到这种模式。到这里OKHttpClient就创建完成了,接下来我们分析Request的创建。

Request的创建

//创建Request
Request request = new Request.Builder().url("http://www.baidu.com").build();

可以发现,Request 也是通过 Builder 模式创建的,进入到 Builder()构造方法

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

内容很简单,只初始化了 method 和请求头 Header 的 Builder 对象。接下来我们看build()方法:

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

很简单,也是跟OKHttpClient的创建一样,传递Builder对象到Request的构造方法中,返回一个Request的对象。其中Request的构造方法源码为:

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的构造方法我们可以发现,Request创建时,对URL、method、请求头header、body等一系列参数进行了初始化。到这里,同步和异步请求就完成了前面两步。接下来我们分析第三步 Call 的创建。

Call的创建流程分析

首先通过前面的学习,我们知道 Call 的创建都是通过调用 OKHttpClient 的 newCall()方法来创建的,我们进入到该方法源码

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

可以发现,由于 Call 是一个接口,具体的实现其实是在 Call 的实现类 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 对象,并对它进行了返回。也可以进入到 RealCall 的构造方法查看具体的内容。到这里,Call 对象就创建成功了。

到这里,前面三步就分析完了,其实前面这三步还只是对发起请求做了一些前期的准备工作,正在发起请求的是第四步,所以接下来,就到了本章最重要的内容,也就是区分 OKHttp 同步与异步请求的第四步。

OKHttp同步与异步请求源码分析

同步请求方法 execute() 源码分析

首先,我们看一下同步请求方法 execute() 源码

@Override 
public Response execute() throws IOException {
    synchronized (this) {
      //这里通过executed参数判断同一个请求是否已经执行,如果已经执行,则抛出异常
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    //收集异常堆栈信息
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      //执行同步请求
      client.dispatcher().executed(this);
      //重要!!通过拦截链获取Response
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      //主动关闭请求
      client.dispatcher().finished(this);
    }
  }

在第一个注释位置,也就是 synchronized 同步请求块中,通过对 executed 标志参数进行判断,实现的效果是:对同一个请求,如果已经执行过,则抛出异常,否则把 executed 置为true。接下来我们看第三个注释位置,通过调用分发器 Dispatcher 的 executed() 方法,执行 OKHttp 的同步请求,我们进入 executed() 源码

/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
    //添加 RealCall 到同步请求队列
    runningSyncCalls.add(call);
}

这里,把 RealCall 添加到同步请求队列中,而这个同步请求队列 runningSyncCalls 是在 Dispatcher 中初始化的,这就到了 OKHttp 中一个很重要的知识点 Dispatcher 分发器中了,在下一章节中,我会对 Dispatcher 源码进行详细的分析,这里先粗略带过。跟同步请求队列一起的,还有异步请求中用到的异步请求队列 runningAsyncCalls 和异步请求等待队列 readyAsyncCalls ,源码如下

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

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

执行完同步请求,下一步就到了 getResponseWithInterceptorChain() 方法 这里,通过这个方法获取响应的 Response,这个方法又是 OKHttp 的一个核心点,其内部会依次调用拦截器来进行相应的操作,后面也会开辟单独的章节进行讲解。

最后在 finally 中,我们可以看到一个很重要的机制,那就是 finished() 方法。它会主动的去回收同步请求,我们看一下源码

/** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    //调用下面的 finished 方法,注意 false 参数
    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!");
      //同步请求时,promoteCalls为false,这里不会执行
      if (promoteCalls) promoteCalls();
      //计算请求的数量
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }
    //当剩余请求数量为0,执行 idleCallback 的 run 方法
    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

它首要的操作就是 synchronized 同步块中的第一行代码,从同步请求队列中移除请求,如果不能移除,就抛出异常。然后因为传入的 promoteCalls 为 false,所以在同步请求时不会走到 promoteCalls() 方法这来,但是在异常请求中会走到这里来。接下来做的就是计算还存在的请求数量总和,具体源码如下

public synchronized int runningCallsCount() {
    //返回异步请求队列与同步请求队列的总和
    return runningAsyncCalls.size() + runningSyncCalls.size();
  }

最后就是判断如果 runningCallsCount==0,即 Dispatcher 中的同步与异步请求队列都为空,且 idleCallback 不为空时,执行 idleCallback 的 run 方法。至此,同步请求的整个流程就分析完了。可以发现 Dispatcher 对同步请求进行的操作很简单,就是把同步请求添加到同步请求队列中,以及从同步请求队列中移除同步请求。接下来我们分析异步请求方法 enqueue() 的流程。

异步请求方法 enqueue() 源码分析

首先我们追踪 Call.enqueue() 方法,因为 Call 是一个接口,所以我们定位到 Call 的实现类 RealCall ,继而找到 RealCall 中的 enqueue() 方法,源码如下

 @Override 
 public void enqueue(Callback responseCallback) {
    synchronized (this) {
      // 首先判断这个Call请求是否已经执行过,执行过抛出异常,否则执行该 Call 请求
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    //新建 AsyncCall 对象,通过 Dispatcher 的 enqueue() 方法执行该 Call 请求
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

这个方法内部做的第一件事,和同步请求方法 execute() 一样,都是去判断当前的 Call 请求是否已经执行,没执行才往下走。然后通过传入 Callback 对象,构建一个 AsyncCall 对象,注意 AsyncCall 这个类,我们先看一下它的源代码

final class AsyncCall extends NamedRunnable {
    ...
}

查看源代码我们发现,AsyncCall 继承了 NamedRunnable 类,我们可以大概猜测这是一个 Runnable,我们继续追踪 NamedRunnable 类的源码

public abstract class NamedRunnable implements Runnable {
    ...

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

到这里我们发现,NamedRunnable 确实是一个 Runnable。在构建了 Runnable 的实现类 AsyncCall 这个对象后,接下来就调用了 Dispatcher 的 enqueue() 方法,而 enqueue() 方法我们通过源代码可以发现它的逻辑就是:首先判断当前正在执行的异步请求是否小于允许的最大值(64),以及正在执行的异步请求的 Host 是否小于允许的最大值(5)。如果条件允许,则把 Call 请求加入到正在执行的请求队列中,并通知线程池去执行这个请求;如果条件不允许,则把 Call 请求加入到异步等待队列中,然后进行下一步的操作。

//最大的请求数
private int maxRequests = 64;
//最大的请求 host 数
private int maxRequestsPerHost = 5;

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

//同步锁
synchronized void enqueue(AsyncCall call) {
    //判断正在执行的异步请求是否小于允许的最大值、以及正在执行的异步请求的 Host 是否小于允许的最大值
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      //条件允许,则把 Call 请求加入到正在执行的请求队列中
      runningAsyncCalls.add(call);
      //通知线程池执行这个请求
      executorService().execute(call);
    } else {
      //条件不允许,则把 Call 请求加入到异步等待队列中
      readyAsyncCalls.add(call);
    }
  }

接下来我们还要重点讲一下 executorService().execute(call);这句代码。我们知道这段代码的重用就是通知线程池去执行请求,即 call 需要去执行它的 run() 方法,而 call 又是一个 Runnable 的实现类。前面我们通过追踪源码发现,在 NamedRunnable 中,run() 方法实现的很简单,但是它提供了一个抽象的 execute() 方法给 AsyncCall 来实现,所以我们重点关注下 AsyncCall 中 的 execute() 方法。

@Override 
protected void execute() {
    boolean signalledCallback = false;
    try {
      //通过拦截链获取请求 Response
      Response response = getResponseWithInterceptorChain();
      //通过判断 retryAndFollowUpInterceptor 拦截器是否取消,返回相应 callback 方法的回调
      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 {
        eventListener.callFailed(RealCall.this, e);
        responseCallback.onFailure(RealCall.this, e);
      }
    } finally {
      //主动finish掉请求
      client.dispatcher().finished(this);
    }
}

首先我们又碰到了前面提过的,OKHttp 中很重要的一个内容 getResponseWithInterceptorChain(),拦截链。这个在后面的文章中会重点拿出来分析,这里我们只要知道,不管是同步请求还是异步请求,都是通过拦截链获取的 Response 。接下来通过判断通过判断 retryAndFollowUpInterceptor 拦截器是否取消,对 Callback 执行不同方法的调用,异步请求的回调就是从这里返回的。最后在 finally 中,同样是前面讲到的主动 finish 请求。至此,整个异步请求的调用流程就分析完了,我们总结如下:
1. 创建一个 OKHttpClient 和 Request 对象
2. 通过 OKHttpClient 和 Request 对象,构建实际 http 请求的 Call 对象,执行 Call 对象的 enqueue() 方法
3. 然后构建 Runnable 的实现类 AsyncCall,通过分发器 Dispatcher,传递给 Dispatcher 的enqueue() 方法。如果 Dispatcher 中的异步请求队列 size 小于最大值64且异步等待队列 size 小于最大值5,就把当前 Call 请求添加进异步请求队列中,然后通知线程池执行请求。否则加入等待队列中,进行后续操作
4. AsyncCall 的执行发生在 AsyncCall 的 execute() 方法中,通过拦截器链获取 Response,然后调用 callback 的不同方法返回对应的结果,最后主动 finish 掉请求

总结

到这里整个 OKHttp 同步与异步请求的主要流程及源码分析就讲完了,其中涉及到了两个 OKHttp 中很重要的知识点:分发器 Dispatcher 和拦截器链 getResponseWithInterceptorChain(),这些在后面的文章都会着重讲解。最后我整理了一套同步与异步请求的UML类图,方便我们从整体的角度来了解这套流程的调用,以及理解框架的思维。
okhttp 同步与异步请求流程图

下一篇 OKHttp源码解析 3:任务调度核心类dispatcher解析

猜你喜欢

转载自blog.csdn.net/qq_14904791/article/details/80106190