阅读 642

源码解析:OkHttp 3.14.x 之执行流程

本文OkHttp源码基于3.14.x,版本下载地址:okHttp 3.14.x

前言

OkHttp是一个非常优秀的网络请求框架,使用方便,操作简单,并且目前比较流行的Retrofit也是默认使用OkHttp。因此从源码深入理解OkHttp是非常有必要的。故今天这篇首先将介绍OkHttp请求的执行流程。另外由于OkHttp从4.x版本开始使用Kotlin来编写,因此今天的源码解析是基于Java版的3.14.x版本。

一、基本使用

既然是开源库,第一步当然先得添加OkHttp的依赖,另外注意得在清单文件声明网络权限。

implementation 'com.squareup.okhttp3:okhttp:3.14.0'
复制代码

我们知道Http常用的Http请求有Get和Post,在这里我们只以Get请求为例子进行分析。

1. 同步GET请求

//在这里也可以通过okHttpClient的new操作来创建OkHttpClient实例
//OkHttpClient okHttpClient = new OkHttpClient();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .connectTimeout(100, TimeUnit.SECONDS) //连接超时时间,100s
                    .readTimeout(100,TimeUnit.SECONDS) //读取超时时间,100s
                    .writeTimeout(100,TimeUnit.SECONDS)//写入超时时间,100s
                    .build();
Request request = new Request.Builder()
        .url("http://wwww.baidu.com")
        .get()//默认就是GET请求,可以不写
        .build();
Call call = okHttpClient.newCall(request);
//开启线程
new Thread(()->{
        try {
            Response response = call.execute();
            //回调响应数据给主线程的调用层
        } catch (IOException e) {
            e.printStackTrace();
        }
	}
}).start();
复制代码

2. 异步GET请求

//在这里也可以通过okHttpClient的new操作来创建OkHttpClient实例
//OkHttpClient okHttpClient = new OkHttpClient();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .connectTimeout(100, TimeUnit.SECONDS) //连接超时时间,100s
                    .readTimeout(100,TimeUnit.SECONDS) //读取超时时间,100s
                    .writeTimeout(100,TimeUnit.SECONDS)//写入超时时间,100s
                    .build();
final Request request = new Request.Builder()
        .url("http://wwww.baidu.com")
        .get()//默认就是GET请求,可以不写
        .build();
Call call = okHttpClient.newCall(request);
//开启线程
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        //需要回到主线程进行操作
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        //需要回到主线程进行操作
    }
});
复制代码

比较同步GET请求和异步GET请求,你会发现其实两者区别并不是很大,主要就是在最后一步提交请求的方式不同,故OkHttp的GET请求的流程可以总结为:

  1. 构造OkhttpClient对象,可以通过new操作直接构造或者通过构造OkhttpClient内部类Builder对象间接构造
  2. 构造Request对象
  3. 构造Call对象
  4. 提交请求,获取响应数据Response。如果是同步请求,则通过Call对象的execute方式提交请求;如果是异步请求,则通过Call对象的enqueue方式提交请求

下面我们将基于源码来分析这4步操作!

二、流程

1. 构造OkHttpClient对象,配置属性

通过上面的分析,我们知道构造OkHttpClient对象的方式有两种,由于直接构造是基于间接构造的基础上,所以我们首先看看间接方式是怎么构造这个OkHttpClient对象的!

1.1 间接构造

通过上面基本方式的介绍,我们知道首先会构造Builder对象,这个Builder是OkHttpClient类的内部类

OkHttpClient.Builder#构造器

  public static final class Builder {
    Dispatcher dispatcher;
    @Nullable Proxy proxy;
    List<Protocol> protocols;
    List<ConnectionSpec> connectionSpecs;
    final List<Interceptor> interceptors = new ArrayList<>();
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    EventListener.Factory eventListenerFactory;
    ProxySelector proxySelector;
    CookieJar cookieJar;
    @Nullable Cache cache;
    @Nullable InternalCache internalCache;
    SocketFactory socketFactory;
    @Nullable SSLSocketFactory sslSocketFactory;
    @Nullable CertificateChainCleaner certificateChainCleaner;
    HostnameVerifier hostnameVerifier;
    CertificatePinner certificatePinner;
    Authenticator proxyAuthenticator;
    Authenticator authenticator;
    ConnectionPool connectionPool;
    Dns dns;
    boolean followSslRedirects;
    boolean followRedirects;
    boolean retryOnConnectionFailure;
    int callTimeout;
    int connectTimeout;
    int readTimeout;
    int writeTimeout;
    int pingInterval;

    public Builder() {
	  //调度器:通过双端队列保存calls,同时在线程池中执行异步请求	
      dispatcher = new Dispatcher();
	  //默认支持的Http协议版本
      protocols = DEFAULT_PROTOCOLS;
	  //okhttp连接配置
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
	  //一个call的状态监听器
      eventListenerFactory = EventListener.factory(EventListener.NONE);
	  //默认的代理选择器
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
	  //默认是没有cookie
      cookieJar = CookieJar.NO_COOKIES;
	  //使用默认的Socket工厂产生Socket
      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;
      callTimeout = 0;
	  //连接超时时间,默认10s
      connectTimeout = 10_000;
	  //读取超时时间,默认10s
      readTimeout = 10_000;
	  //写入超时时间,默认10s
      writeTimeout = 10_000;
	  //和WebSocket有关。为了保持长连接,我们必须间隔一段时间发送一个ping指令进行保活;
      pingInterval = 0;
    }
复制代码

通过注释我们可以知道Builder的构造器中初始化了一系列重要参数的默认值,比如调度器Dispatcher。也可以在里面找到我们熟悉的连接超时时间,读取超时时间,写入超时时间值的初始化。那我们要修改这些默认值该怎么办呢?没错,Builder还提供了一系列方法供我们修改默认值

OkHttpClient.Builder

    public Builder readTimeout(long timeout, TimeUnit unit) {
      readTimeout = checkDuration("timeout", timeout, unit);
      return this;
    }


    public Builder writeTimeout(long timeout, TimeUnit unit) {
      writeTimeout = checkDuration("timeout", timeout, unit);
      return this;
    }

    public Builder connectTimeout(long timeout, TimeUnit unit) {
      connectTimeout = checkDuration("timeout", timeout, unit);
      return this;
    }
......
复制代码

最后再通过Builder的build来构造OkHttpClient对象

OkHttpClient.Builder#build

    public OkHttpClient build() {
      //this为自身Builder对象  
      return new OkHttpClient(this);
    }
复制代码

可以发现其实一系列下来就是利用Builder设计模式来构造OkHttpClient对象,然后我们来看看OkHttpClient又做了哪些事

OkHttpClient#构造器

  //设置默认值
  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);
    .....
  }
复制代码

OkHttpClient做的事情很简单,就是将Builder对象的值赋值到自己的成员变量中,于是OkHttpClient对象构造成功。

1.2 直接构造

直接构造只需一行代码

OkHttpClient okHttpClient = new OkHttpClient();

我们继续看下OkHttpClient的构造器

OkHttpClient#构造器

  public OkHttpClient() {
  	//构建了Builder对象,然后调用Builder对象为参数的构造器
    this(new Builder());
  }
复制代码

看到这里是否突然明白,原来直接构造也是构造了Builder对象的,然后再将Builder对象的默认值赋值给OkHttpClient对象的成员变量中,这么一看好像直接构造和间接构造好像也没什么区别啊?

真的是这样吗,其实并不是,细看的话你就会发现通过直接构造只能构造出默认值,而通过间接构造就可以修改默认值。并且如果想添加拦截器的话,就必须得通过间接构造。

1.3 小结

构造OkHttpClient对象有两种方式,两者的使用场景如下:

  • 如果直接使用默认值的话,可以直接使用new操作来构造OkHttpClient对象。
  • 如果想自行设置其中的属性,比如连接超时时间或者添加拦截器的话,必须得首先构造Builder对象,然后设置相关的属性,最后调用Builder对象的build来构造OkHttpClient对象。

2. 构造Request对象,配置属性

Request对象的构造也是通过Builder模式来构造的,代码来说也是比较容易看懂的。我们首先看Request中的Builder的构造方法

Request.Builder#构造方法

    public Builder() {
	  //默认是GET请求	
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

复制代码

可以发现在Builder构造中已经设置了GET的请求方法了,所以如果是GET请求的话后面其实可以不需要自己设置了。然后我们还需要设置请求地址。

Request.Builder#url

    /**
     *设置请求地址
     */
    public Builder url(String url) {
      if (url == null) throw new NullPointerException("url == null");

      //判断是http还是https地址
      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);
      }
      //解析地址,判断地址是否有效 
      return url(HttpUrl.get(url));
    }

复制代码

设置完请求地址后,我们需要通过build方法来构造Request对象

Request.Builder#build

    public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      //构建Request对象
      return new Request(this);
    }

复制代码

Request#构造方法

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tags = Util.immutableMap(builder.tags);
  }

复制代码

在Request的构造器中将Builder的值赋值到自己的成员变量中,于是Request构造成功。

3. 构造Call对象

通过基本使用我们知道Call对象会调用OkHttpClient对象的newCall来构造

OkHttpClient#newCall

  @Override public Call newCall(Request request) {
    //this为自身OkHttpClient对象  
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

复制代码

在这个方法中,其实主要目的是将前面构造的OkHttpClient对象和Request对象传给RealCall的newRealCall方法中

RealCall#newRealCall

  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
  }

复制代码

而这个newRealCall方法也是很简单,就是构造了RealCall对象,RealCall对象保存了OkHttpClient对象和Request对象,所以最后构造出来的Call对象其实就是RealCall对象。

4.提交请求, 获取响应数据Response

终于来到执行流程的重头戏了,我们首先分析同步请求的提交。

4.1 同步

同步请求是调用了Call对象的execute来提交请求,不过需注意的是这种方式会阻塞线程,所以在使用的时候应该开启子线程然后再调用execute,否则就会引起ANR。通过上面的分析我们已经知道Call对象其实是RealCall对象,所以会调用RealCall对象的execute方法来提交请求。

RealCall#execute

  // 同步Call,一个Call只能被执行一次
  @Override public Response execute() throws IOException {
    synchronized (this) {
	  //executed表示是否执行
	  //此时表示为再次被调用,但是一个Call只能被执行一次,所以抛出异常
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.timeoutEnter();
    transmitter.callStart();
    try {
	  //此时this为RealCall对象
      client.dispatcher().executed(this);
	  //调用各拦截器对请求进行处理
      return getResponseWithInterceptorChain();
    } finally {
	  //不管是否执行成功都关闭当前请求任务
      client.dispatcher().finished(this);
    }
  }

复制代码

仔细观察execute方法,你就会发现其实这个方法主要做了三件事:

  1. 将请求加入到同步任务的执行队列
  2. 调用各拦截器对请求进行处理,获取响应数据
  3. 关闭当前请求任务

我们接下来将根据这3件事来进行分析!

1. 将请求加入到同步任务的执行队列

在execute方法中通过一行代码来实现这个任务

client.dispatcher().executed(this);

此时的client此时就是OkHttpClient对象,dispatcher就是在构造OkHttpClient对象时初始化的Dispatcher对象,所以我们接下来需要看Dispatcher的executed方法

Dispatcher#executed

  //同步任务的执行队列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  //同步请求,将该同步任务添加到正在执行的Deque中
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

复制代码

在这里runningSyncCalls是ArrayDeque对象,ArrayDeque既可以作为栈使用,效率高于Stack,又可以作为队列使用,效率也比LinkedList更好一点,在这里就是当作双端队列来使用,所以runningSyncCalls就是同步任务的执行队列,而Dispatcher的executed方法也很简单,就是简单的将同步请求添加到执行队列的队尾。

2. 调用各拦截器对请求进行处理,获取响应数据

下列只介绍拦截器链的大概调用流程。由于拦截器链算的上是整个框架的精髓,考虑到篇幅问题,对拦截器链的详细介绍将会在下一篇文章进行分析。

我们回到RealCall的execute方法,通过调用getResponseWithInterceptorChain来获取响应数据

RealCall#getResponseWithInterceptorChain

  Response getResponseWithInterceptorChain() throws IOException {
    //添加一系列的拦截器,注意添加的顺序
    List<Interceptor> interceptors = new ArrayList<>();
	//添加构造HttpClient对象时设置的拦截器
    interceptors.addAll(client.interceptors());
	//重试,重定向拦截器
    interceptors.add(new RetryAndFollowUpInterceptor(client));
	//桥接拦截器,桥接应用层和网络层,添加必要的头
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
	//缓存拦截器,从缓存中拿数据
    interceptors.add(new CacheInterceptor(client.internalCache()));
	//网络拦截器,建立网络连接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
	  //通过okhttpClient构造时的addNetworkInterceptor拦截器
      interceptors.addAll(client.networkInterceptors());
    }
	//服务器请求拦截器,向服务器发起请求获取数据
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //构建责任链的第一个结点,第4个参数为0,即该结点的位置为0
    //结点对应了相对位置的拦截器
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
	  //调用proceed方法来处理责任链	
	  //责任链设计模式
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

复制代码

在这个方法中,首先会添加一系列的拦截器,按添加的顺序分别是:

  • 构造okHttpClient对象时通过addInterceptor配置的全局拦截器
  • 重试,重定向拦截器
  • 桥接拦截器,桥接应用层和网络层,添加必要的头
  • 缓存拦截器,从缓存中拿数据
  • 网络拦截器,建立网络连接
  • 构造okhttpClient对象时通过addNetworkInterceptor配置的非网页请求拦截器
  • 服务器请求拦截器,向服务器发起请求获取数据

可以看出各拦截器各司其职,由于在这里使用的是类似责任链的设计模式,所以在这里需要特别注意添加的顺序,因为添加的顺序就是执行拦截器的顺序。

然后接着构造了拦截器链的头结点,这里需注意其中的第4个参数为0,即该结点对应了0位置的拦截器。

最后调用了该结点的proceed方法来处理该请求。让我们看看这个proceed方法

RealInterceptorChain#proceed

    @Override public Response proceed(Request request) throws IOException {
    return proceed(request, transmitter, exchange);
    }


    public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      throws IOException {
    //传入的index如果大于添加的拦截器列表的大小,则抛出异常    
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    ...

    //构造了责任链的下一个结点,重点关注第四个参数,传入的为当前index+1
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
	//拿到当前位置的拦截器
    Interceptor interceptor = interceptors.get(index);
	//调用拦截器的intercept方法来处理该请求,并将下一请求作为参数传递进去
    Response response = interceptor.intercept(next);
    .....

    return response;
  }

复制代码

这个方法首先构造了头结点的下一个结点,其位置为index+1。然后根据index和拦截器列表拿到当前位置的拦截器,最后再调用当前拦截器的intercept方法来处理请求,这里还需特别注意的是再处理请求时也将拦截器链的下一节点传进去了。接着就是责任链模式的传递过程,在这里我们假设构造OkHttpClient没有添加全局拦截器,所以根据上面的分析,头结点的拦截器应该是RetryAndFollowUpInterceptor,错误重连拦截器。

RetryAndFollowUpInterceptor#intercept

  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();
    .......
      Response response;
      boolean success = false;
      try {
        //realChain就是下一节点
        response = realChain.proceed(request, transmitter, null);
        success = true;
      } 
      ......
    }
  }

复制代码

这个拦截器的详细工作会在下篇文章OkHttp 3.14.x 源码解析-拦截器中分析,这里我们只分析大概的执行流程。可以发现这个拦截器还会调用下一结点的proceed方法,这样就可以实现递归遍历在一开始添加的拦截器列表中Interceptor中的intercept方法。当遍历到最后一个服务器请求拦截器时,会直接返回Response,然后又将这个Response一直向上返回,最后返回拦截器链处理过后的响应数据。

3. 关闭当前请求任务

不管有没有得到响应数据,最后我们都得关闭当前的请求任务,我们继续看回RealCall的execute方法

  @Override public Response execute() throws IOException {
    synchronized (this) {
	.....
    try {
      client.dispatcher().executed(this);
      return getResponseWithInterceptorChain();
    } finally {
	  //不管是否执行成功都关闭当前请求任务
      client.dispatcher().finished(this);
    }
  }

复制代码

这时候会调用Dispathcer的finished方法来关闭当前请求任务,需注意this为RealCall对象

Dispatcher#finished

  //同步请求
  void finished(RealCall call) {
    finished(runningSyncCalls, call);
  }

  //同步和异步请求
  private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
	  //将请求从执行队列中移除
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }
    //判断是否有需要执行的异步任务
    boolean isRunning = promoteAndExecute();
	//当所有请求执行完毕后执行这个runnable
    if (!isRunning && idleCallback != null) {
      idleCallback.run();
    }
  }

复制代码

从finished方法可以看出,其实关闭当前请求任务就是将该请求从执行队列中移除,而promoteAndExecute方法其实是异步任务完成后处理,同步任务执行promoteAndExecute方法是没有效果的。

到这里同步获取响应数据分析完毕!接着让我们继续分析异步请求是如何获取响应数据的。

4.2 异步

异步是通过ReaCall的enqueue来提交请求的,我们看看enqueue方法

RealCall#enqueue

  //异步call,一个call只能被执行一次
  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
	  //一个call只能被执行一次	
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.callStart();
	//调用调度器Dispatcher的enqueue方法,此时传入的是AsyncCall对象,其实现了Runnable接口
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

复制代码

这里的responseCallback其实就是我们在使用异步请求时的匿名Callback类的回调对象,在这里根据Callback回调接口对象构造了AsyncCall对象,然后调用了Dispatcher的enqueue方法

Dispathcer#enqueue

   //异步任务的就绪队列
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  void enqueue(AsyncCall call) {
    synchronized (this) {
	  //将请求放到就绪队列	
      readyAsyncCalls.add(call);

      //判断是否为WebSocket
      if (!call.get().forWebSocket) {
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    //准备执行
    promoteAndExecute();
  }

复制代码

跟同步请求一样,这里的readyAsyncCalls也是双端队列,不过readyAsyncCalls并不是执行队列,而是异步任务的就绪队列,然后调用promoteAndExecute方法

Dispathcer#promoteAndExecute

  private int maxRequests = 64;//允许执行的最大请求数
  private int maxRequestsPerHost = 5;//一个主机允许执行的最大请求数
  //异步任务的执行队列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  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();
		//如果正在执行的异步数量达到了最大要求64,则跳出循环	
        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        //如果单个Host正在执行的请求大于最大要求5个,则该请求跳过
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

		//否则,将该请求从异步就绪队列中移除
        i.remove();
        asyncCall.callsPerHost().incrementAndGet();
		//添加到异步任务的集合中
        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;
  }

复制代码

这个方法首先要遍历就绪队列,OkHttp对执行任务的数量做了要求:

  • 当前正在执行的异步任务不能超过64个
  • 一个主机正在执行的异步任务不能超过5个

如果满足上述条件的话,就将当前请求从异步任务的就绪队列中移除,并且添加到异步任务的集合和异步任务的执行队列中。接着就继续遍历当前要执行的异步任务,然后执行异步任务。

在执行异步任务时可以发现这里调用了executorService方法。我们来瞧瞧

Dispatcher#executorService

  public synchronized ExecutorService executorService() {
    //单例实现:返回了一个执行线程池的对象
    if (executorService == null) {
	  //核心线程数为0,空闲存活期60s
	  //容纳的最大线程数为Max,实际情况下并不会超过64,因为上面的enqueue已经做了判断	
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

复制代码

这个方法也挺简单的,就是返回了一个执行线程池的对象,接着就会调用AsyncCall的executeOn方法

RealCall.AsyncCall#executeOn

    void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
	  	//线程池执行任务,this为AsyncCall对象
        executorService.execute(this);
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        transmitter.noMoreExchanges(ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }

复制代码

这个方法就是调用了刚传进去的执行线程池的对象来执行任务,然后就会执行AsyncCall的run方法。然后你就会发现AsyncCall中根本没有run方法,想必机智的你应该想到下一步应该怎么做了,没错!在AsyncCall父类找这个方法

NamedRunnable

  final class AsyncCall extends NamedRunnable {
      ....
  }

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }
  //AsyncCall没有run方法,在父类找到run
  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
	  //重点关注
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }
  //空实现,实际调用了子类,即AsyncCall的execute方法	  
  protected abstract void execute();
}



复制代码

果然如此,但是在NamedRunnable的run方法中又会重新调用AsyncCall的execute方法,兜兜转转,又回到了AsyncCall中

AsyncCall#execute

    @Override protected void execute() {
      boolean signalledCallback = false;
      transmitter.timeoutEnter();
      try {
	  	//调用各拦截器对请求进行处理,获取数据
        Response response = getResponseWithInterceptorChain();
        signalledCallback = true;
	    //获取数据成功,调用callback的onResponse将响应数据返回给调用层
        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 {
          //如果捕获到异常,回调Callback对象的onFailure方法
          responseCallback.onFailure(RealCall.this, e);
        }
      } catch (Throwable t) {
        cancel();
        if (!signalledCallback) {
          IOException canceledException = new IOException("canceled due to " + t);
          canceledException.addSuppressed(t);
		  //捕获到异常,回调Callback对象的onFailure方法
          responseCallback.onFailure(RealCall.this, canceledException);
        }
        throw t;
      } finally {
	  	//无论是否获取到数据,最后都要调用Dispatcher对象的finished方法
        client.dispatcher().finished(this);
      }
    }
  }

复制代码

仔细看这个方法,你会发现这个方法其实主要工作有三件:

  1. 调用各拦截器对请求进行处理,获取响应数据
  2. 将请求结果回调给调用层
  3. 关闭当前请求任务

按照老套路,我们将对这三件工作逐个分析。

1. 调用各拦截器对请求进行处理,获取响应数据

这个分析与同步请求时的分析是一样,这里不再进行分析,忘记的小伙伴可以向上翻翻

2. 将请求结果回调给调用层

这里的回调仍然是在子线程中,在实际使用时需要回到主线程对响应数据进行操作。

当获取到响应数据时,会调用responseCallback的onResponse将响应数据回调出去,这里的responseCallback就是我们在实际使用时用匿名内部类的构造的Callback对象,通过回调就能执行Callback中的onResponse方法。如果捕获到异常就回调Callback的onFailure方法。

3. 关闭当前请求任务

不管请求结果是否成功,都要关闭当前的请求任务,这里跟同步关闭请求任务一样,调用了Dispatcher的finished,不过注意参数不一样,这里传递的参数为AsyncCall对象

Dispatcher#finished

  //异步请求
  void finished(AsyncCall call) {
    call.callsPerHost().decrementAndGet();
    finished(runningAsyncCalls, call);
  }

  //同步和异步请求
  private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
	  //将请求从执行队列中移除
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }
    //判断是否有需要执行的异步任务
    boolean isRunning = promoteAndExecute();
	//当所有请求执行完毕后执行这个runnable
    if (!isRunning && idleCallback != null) {
      idleCallback.run();
    }
  }

复制代码

首先还是得将请求完任务从异步任务的执行队列中移除,然后还需要判断是否有需要执行的任务,这里又会调用promoteAndExecute方法,这个方法是否似曾相识呢?其实这个方法已经在上面分析过了,就是接着执行下一个异步任务。

4.3 小结

同步和异步的GET请求的区别就在于提交请求这一流程不同,两者提交请求的不同之处在于:

  • 异步提交请求比同步多了一个就绪队列,原因是异步一次能够执行多个任务,而同步一次只能执行一个任务
  • 关闭当前请求任务时,同步只是将当前请求任务从同步任务的执行队列中删除,而异步将请求从异步任务的执行队列中移除后,还会遍历就绪队列来执行下一次任务。

总结

到这里OkHttp的同步和异步请求的执行流程就分析完毕了。我们可以稍微歇会,然后一鼓作气,继续探索OkHttp的神秘王国-OkHttp 3.14.x 源码解析-拦截器

参考文章:

关注下面的标签,发现更多相似文章
评论