OkHttp3源码分析(二)-Dispatcher和Interceptor

对OkHttp介绍,我们分为使用篇源码分析篇两个系列进行介绍。

上一篇文章中,我们大致梳理了一下OkHttp的请求过程。但是中间涉及到的client.dispatcher().executed(this);client.dispatcher().enqueue(new AsyncCall(responseCallback));getResponseWithInterceptorChain()这几个过程我们并没有进入去详细分析,只是提了每一个方法的作用。所以本篇文章的内容就是对这几个方法进行说明。

首先,前两个方法都是通过client.dispatcher()获得了Dispatcher对象,然后调用它对应的方法。这里我们就先介绍下Dispatcher这个OkHttp框架中的任务调度类。

1 Dispatcher

在纤细介绍该类之前,我们先看下为什么通过client.dispathc()就能获得到Dispatcher对象了,也就是先介绍下该对象的创建过程是怎么样的。

1.1 Dispatcher的创建过程

各位应该还记得我们OkHttp的基本使用方式吧:

OkHttpClient client = new OkHttpClient();

  String run(String url) throws IOException {
    Request request = new Request.Builder()
        .url(url)
        .build();

    try (Response response = client.newCall(request).execute()) {
      return response.body().string();
    }
  }

第一步就是创建OkHttpClient对象,我们看下它的构造方法:

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

创建Builder后,调用了它的有参构造,我们先看下Builder的构造过程:

public Builder() {
      dispatcher = new Dispatcher();
      ……
    }

这里省略一些代码,但是可以看到,在Builder的构造方法中,初始化了一个Dispatcher对象。然后我们在回到上一步流程,调用OkHttpClient的有参构造:

OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    ……
  }

也省略了无关代码,可以看到,这里就将Builder中的dispather赋值给了OkHttpClient中的dispatcher属性。所以我们通过client.dispater()就能获取到Dispatcher对象了:

public Dispatcher dispatcher() {
    return dispatcher;
  }

Dispatcher怎了来的搞清楚了,我们接着就看下它是怎么起到任务调度作用的。

1.2 Dispater简介

在具体跟踪分析上述提到的excuted(Call)equeue(AnsycCall)之前,我们先来简单看下Dispatcher这个类的成员变量,这有助于我们后面的流程分析。

public final class Dispatcher {
  // 最大请求数目
  private int maxRequests = 64;
  // 统一个Host最大请求数目
  private int maxRequestsPerHost = 5;
  

  // 线程池 用于执行异步请求任务
  private @Nullable ExecutorService executorService;

  // 准备发起异步请求的双向队列
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  // 正在发起异步请求的双向队列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  // 正在发起同步请求的双向队列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  ……
}

这里列出了几个比较关键的字段。其中maxRequests规定了同一个OkHttpClient中可同时发起的最大请求数,maxRequestsPerHost规定了统一Host中可同时发起的最大请求数。接着executorService是一个线程池,最终我们的异步任务就是通过该线程池执行的。从这我们可以看到,并没有在这里直接进行初始化,OkHttp框架进行了一种懒加载策略的,在它的getter方法中可以看到它的创建:

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

可以看到,该线程池的核心线程数为0,保活60s,也就意味着空闲线程在等待60秒后如果还没有使用就会被回收。以Integer.MAX_VALUE为最大线程数数量,我们也就可以认为该线程池是没有上界限制的,只要有合法请求,就会创建新的线程,这里的合法请求是指没有超过maxRequestsmaxRequestsPerHost的限制。
剩下还有三个双向队列的属性,注释里面已经说的比较清楚了,这里就不再赘述了。

1.3 Dispatcher任务调度过程

有了上面的基础,就可以进入任务调度过程分析了,这里我们还是先看同步任务,这里贴出RealCall.execute方法

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    timeout.enter();
    eventListener.callStart(this);
    try {
      // 加入队列
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      e = timeoutExit(e);
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      // 请求完成,移除队列
      client.dispatcher().finished(this);
    }
  }

直接跟进去看下Dispatcher.executed(ReallCall)方法:

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

非常简单,就是将传入的RealCall加入了runningSyncCalls这个队列中。接着看上面的流程,当请求完成后最终会调用Dispatcher.finishd(ReallCall)方法,我们进去看下:

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

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

此时这里的参数calls就是runningSyncCalls对象,所以这里第一步就是将之前的Call移除队列。然后执行了promoteAndExecute();这个方法,该方法如果返回fasle说明当前没有请求任务了,所以后续做了判断,如果没有任务,idleCallback也不为null的话,就会回调该对象的run。所以如果我们想在空闲的时候做一些处理,就可以设置IdleCallback接口。接着进入promoteAndExecute();方法里面具体看下是怎么处理的:

private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      // 从readyAsyncCalls取出任务,加入executableCalls集合
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();

        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
        executableCalls.add(asyncCall);
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }
	// 从executableCalls里面一次取出AsyncCall对象,并调用它的executeOn()方法
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }

这个方法里面就是从readyAsyncCalls队列中取出待执行的异步任务,调用异步任务的executeOn(executorService())方法。其中参数是之前所述的线程池对象——executorService。到这里就和上篇分析的异步请求流程接上了,这里就不再往下分析了。
到这里,同步调度过程就分析完了,可以看到,同步请求任务并没真正由Dispatcher来调度,只是在其内部维护了一个双向队列而已。所以Dispatcher主要目的还是用来调度异步任务的,接下来看异步调度过程。
还是从ReallCallenqueue(Callback)方法开始跟踪:

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

比较简单,通过我们传入的Callback对象构造了一个AsyncCall,然后调用了Dispatcherenqueue(AsyncCall)方法,跟进去:

void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);
    }
    promoteAndExecute();
  }

就是将call加入到readyAsyncCalls队列中,然后调用了promoteAndExecute();这个方法,前面已经分析过了,这里不再赘述。通过上篇文章的分析,我们知道,异步请求,最终在AsyncCall对象中执行excute()方法的时候,最终也会调用Dispatcher.finish(AsyncCall)方法,最终会调用finished(Deque<T> calls, T call)这个方法,这个方法前面已经分析又会去执行promoteAndExecute();方法,直到任务队列中没有待执行的任务。

到这,整个整个任务的调度就分析完了。接下来我们接着看下上篇没有详细说明的getResponseWithInterceptorChain();方法。

2 Interceptor调用链

上篇文章我们只是说了getResponseWithInterceptorChain();会进入到我们的设置的Interceptor中,并最终返回请求的数据。具体是怎么实现的呢?我们跟进去看下:

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //添加开发者应用层自定义的Interceptor
    interceptors.addAll(client.interceptors());
    //这个Interceptor是处理请求失败的重试,重定向
    interceptors.add(retryAndFollowUpInterceptor);
    //这个Interceptor就是OkHttp框架自动是添加一些额外的支持
    //(如:使用篇里面说到的gzip压缩支持就是在这里面处理)
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //这个Interceptor的职责是判断缓存是否存在,读取缓存,更新缓存等等
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //这个Interceptor的职责是建立客户端和服务器的连接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
    //添加开发者自定义的网络层拦截器
      interceptors.addAll(client.networkInterceptors());
    }
    //一个包裹这request的chain
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
//这里就把chain传递到第一个Interceptor调用链中了
    return chain.proceed(originalRequest);
  }

这里创建了一个ArrayList,让后不断的往这个list里面添加Interceptor。至于每个Interceptor的作用,代码注释里面已经说明了。最后通过RealInterceptorChain.proceed(Request)方法进入了Interceptor的调用链

2.1.5 RealInterceptorChain.proceed()

接着上面的步骤,我们看下怎么通过proceed()方法就进入了Interceptor的调用链的。分析这个方法之前,先注意上面创建RealInterceptorChain对象的初始化参数:

Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

这里传入了我们的Interceptor的List集合和Request对象,同时初始化index=0(第五个参数)。
接着看下proceed()方法:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    ……
    // 创建下一个Interceptor所需的RealInterceptorChain
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
	// 获取index所在的Interceptor,并调用该Interc的intercept(Chain)方法
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
……

    return response;
  }

这里暂时只需要我们先把整个调用链的流程搞清楚,所以就只留下了关键代码。其他内容,后续文章分析网络请求的时候,还会详细说明。
根据上面的描述我们知道当前这个Chain对象中的index=0,所以Interceptors.get(index)是第一个Interceptor,紧接着又使用Request和index+1的索引构建了下一个Chain对象。然后进入第一个Interceptor的intercept(Chain)方法,传入的是新的Chain对象。根据之前使用篇里面学习的知识,我们知道在这个方法中,如果我们需要放行一般会调用chain.process(Request)方法,这样就会进入上面新创建的Chainprocess(Request)会进入该方法里面,此时index=1的,所以又会取出下一个Interceptor出来,以此类推,一层一层往下执行,最终就形成了Interceptor的调用链。
根据以上分析,我们可以画出大致的调用链的图如下:
Interceptor调用链
这里只做整体流程分析,具体每个Interceptor里面做的事情就不做详述了。最后一个CallServerInterceptorintercept(Chain)方法里面里面是没有调用chain.proceed()方法的,直接从网络获取Response并返回了,又一层一层的返回到最上面getResponseWithInterceptorChain方法中。最终就形成了一个“U”型结构的调用链。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章