OkHttp(二) - 请求流程分析

上一篇文章简要的介绍了OkHttp的API简单使用,通过创建了OkHttpClient和Request这些对象就能对远程请求建立连接,获取数据。本篇文章将对OkHttp的请求流程做更进一步的深入分析,从源码的角度来看看它的请求流程是具体怎么执行的。

请求方式


请求流程

OkHttp提供了两种请求方法,分别是同步和异步。其实两种方式唯一的区别就在于,异步方式是将我们的请求放入了一个线程池来执行的,其具体的底层请求实现机制都是一样的。这里先说一下大概的请求流程,以便在看后续的代码分析过程中能保持清晰的思路。

我们的请求都是通过一个OkHttpClient的对象来发起的,这个对象是个入口类,保存了用户的配置信息和一些上下文环境信息。Call是一个接口,可以抽象成用户发起的请求调用,具体的实现类是RealCall这个对象,Requset就是具体的请求,保存了你要请求的地址,ip,端口等信息。

客户端OkHttpClient发起调用请求,首先创建一个RealCall对象,然后根据用户是选择同步还是异步方式来执行不同流程。如果是同步方式,则直接执行,如果是异步方式,则将请求放入线程池来执行,最后通过一个拦截器链来完成具体的请求过程并获取响应。这个拦截器链是整个OkHttp框架的核心,我会在后面详细地介绍,这里你可以就把它理解为一个执行具体请求的对象即可。用一个图来描述吧,下图就是请求流程的过程图:

下面就深入代码来看看两种不同的执行方式是怎么实现的

同步请求

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://www.baidu.com").build();
Response response = client.newCall(request).execute()
复制代码

从OkHpptClient的入口newCall方法说起:

  1. 执行newCall,创建一个具体的请求调用对象RealCall
  @Override public Call newCall(Request request) {
    //创建一个具体的请求调用对象RealCall
    return new RealCall(this, request, false /* for web socket */);
  }

复制代码
  1. RealCall通过excute方法执行具体的请求
@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      //将当前的请求加入runningSyncCalls队列
      client.dispatcher().executed(this);
      //执行请求获取响应
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }
复制代码

在执行请求前会将当前的call加入一个叫做runningSyncCalls的队列中,用于请求调用过程中的状态管理和跟踪处理,RealCall共有三个队列:

  • readyAsyncCalls
    复制代码
    异步请求准备队列,当异步请求数量达到上限,会将请求保存于此队列中,待后续处理
  • runningAsyncCalls
    复制代码
    当前正在执行的异步请求队列,存储当前正在执行的请求
  • runningSyncCalls
    复制代码
    当前正在处理的同步请求队列
  1. 调用拦截器链完成请求处理
    Response result = getResponseWithInterceptorChain();
    复制代码

上面这段代码是整个OkHttp框架中最核心的一句,它内部实现是通过一系列的拦截器组成了拦截器链,来完成请求的具体请求过程,包括重试,请求体处理,响应头处理,响应体处理等逻辑。这里我们不对它进行展开说明,你只需知道我们的请求是通过一个拦截器链来请求和获取响应即可。上面的流程时序图如下所示:

异步请求

调用Call对象的excute方法执行同步请求,调用enqueue(Callback responseCallback)就会执行异步请求,该方法需要传入一个响应回调接口,用于响应请求执行成功或执行失败时的操作。

  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      //标志状态
      executed = true;
    }
    captureCallStackTrace();
    //将请求加入队列,并通过线程池来执行
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
复制代码
  1. 请求入队 通过dispatch对象,将请求加入队列
      synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      //如果已达到当前最大的处理请求数,则排队等待
      readyAsyncCalls.add(call);
    }
  }
复制代码

首先会判断是否达到了请求上限,这里的请求上限包含两种:一、总的最大并发请求数;二、每个host绑定的最大并发请求数。如果两个都没有达到上限则会直接加入线程池进行处理。再来看看创建线程池的代码

  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }
复制代码

这里的线程池没有核心线程,也就是说当没有请求时不会有空闲的线程消耗资源,但是最大线程数为Integer.MAX_VALUE,似乎与我们平时的一些规范是冲突的。但还记得前面刚说过的请求上限吗,其实这个上限数就限制了最大能创建的线程数,所以不用担心。

  1. 如果没有达到请求上限,调用线程池执行请求
    executorService().execute(call)
复制代码

执行的call对应的实现类为AsyncCall,实际上它继承了Runable()接口,在这个对象的execute()方法中实现了具体的请求逻辑

    @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 {
        //完成请求处理,从runningSyncCalls队列中移除当前请求对象,从readyAsyncCalls中出队请求,加入runningSyncCalls,并调用线程池执行
        client.dispatcher().finished(this);
      }
    }
  }
复制代码

具体执行请求的方法与同步请求方式中一样,都是调用拦截器链来处理的

Response response = getResponseWithInterceptorChain();
复制代码
  1. 如果请求达到请求上限,则加入等待队列,这个在前面第一步中请求进入队列时已经说了,参考前面介绍

  2. 每执行完一个请求,从等待队列中按顺序出队,进入线程池执行。在ASyncalCall对象的process方法中,最后都会执行finally方法体,调用finish方法从等待队列中取出请求并执行

  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;
    }
复制代码

具体看看promoteCalls方法,它完成了从等待队列中取出请求并执行请求的逻辑

  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.
    }
  }
复制代码

总结

至此,同步请求和异步请求的流程就分析完了,从中我们可以看出还是比较简单,但是请求的具体执行执行流程是怎么实现的,也就是说怎么在拦截器处理链中处理的,这个是框架最核心的部分,我们还没有具体分析,在后面的篇幅中我们将重点介绍。

猜你喜欢

转载自juejin.im/post/5d463fb0e51d4561e62370db