okhttp3 简单源码解析

版权声明:本文为博主原创文章,转载必须注明出处!! https://blog.csdn.net/qq_34902522/article/details/79483678

前言

对OKhttp的源码看了有一段时间了,感觉有点体会,准备写点东西,深刻深刻,不然过段时间就忘了O(∩_∩)O哈哈~
网上已经有了很多对OKhttp3的源码分析文章,说的也都比较好,但是觉得还是看别人写的再好终究是别人的东西,自己写的过程中会对OKhttp的理解更加深刻,而且1000个人的眼里有一千个哈姆雷特,每个人对OKhttp的理解,认识,还是会有不同的。
对OKhttp的源码分析,一篇文章也是说不完的,能说完的话,那也太省略,长篇大论的文章,也很难让人看下去,所以,准备写一个关于OKhttp源码的分析的系列文章,本篇主要写的是OKhttp的工作流程,并对流程中的要点进行简要概述,后续的文章中在精要分析。

概述

官网OverView
看官方的最准确。

基本用法

先看基本用法,通过他的用法,来分析他的原理。
OKhttp请求分为同步请求和异步请求,代码如下:
同步请求:

OkHttpClient client = new OkHttpClient();

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

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

异步请求:

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
                .url(url)
                .build();
client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

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

            }
        });

同步请求比较简单,所以我们来分析,异步请求,异步请求的原理明白了,同步的自然而然就知道了。

流程源码剖析

从代码中我们不难看出来,OKhttpClient,Request,Response这些都是比较重要的类。我们看OKhttpClient的构造方法 new OKhttpClient();

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

他的无参构造方法中调用了有Builder参数的构造方法,自己new 了一个Builder对象穿了进去。我们接着看new Builder()方法。

public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      ...
      ...
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

发现该构造方法中初始化了一些对象,赋值了变量。

OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    ...
    ...
    this.connectTimeout = builder.connectTimeout;
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
    this.pingInterval = builder.pingInterval;

    if (interceptors.contains(null)) {
      throw new IllegalStateException("Null interceptor: " + interceptors);
    }
    if (networkInterceptors.contains(null)) {
      throw new IllegalStateException("Null network interceptor: " + networkInterceptors);
    }
  }

上面代码我们看到,把Builder的变量的值赋给了OKhttpClient,我省略的部分代码,不然代码量会比较多。
这种Builder建造者模式的设计在OKhttp项目中有很多,比如Request,Response,CacheControl等类,都采用了Builder模式来设计。比较灵活方便。
然后我们看Request.Builder.url(url)方法。

public Builder url(String url) {
      if (url == null) throw new NullPointerException("url == null");

      // Silently replace web socket 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);
    }

这个方法的作用,是对url进行的判断,转化。如果传进来的url字符串是一个无效的http,https URL,会出现IllegalArgumentException,为了避免这种情况,就出现了这个方法,通过调用HttpUrl.parse()方法来进行转化,如果是无效的http,https URL,会返回null,而不出现IllegalArgumentException。
通过上面的方法,把要请求的url设置进去之后,接下来就调用了client.newCall(request).enqueue(Callback)请求代码,我们看一下newCall(request)的源码:

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

我们发现他是创建了一个RealCall对象,RealCall是实现Call接口,enqueue()方法是Call接口的方法,所以,运行enqueue()方法的是RealCall对象,所以我们来看RealCall对象里面的enqueue()方法。
okhttp里面的Call概念是一个将要被执行网络请求,可以被取消,只可以被执行一次。

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

这里我们看主要的代码client.dispatcher().enqueue(new AsyncCall(responseCallback)); 这里我们发现,继续调用了Dispatcher的enqueue()方法,此刻OKhttp里面很重要的一个类出现在我们面前了,那就是Dispatcher(调度分配器)。其实之前在OKhttpClient对象创建的时候,就有dispatcher的身影了,不过这次是比较明显的看到 。。。

Dispatcher (调度分配器)

Dispatcher是一个主要在异步请求时会用到的一个策略,我看有的文章说的比较绝对,说只有在异步请求才会用到,其实同步也有用到,只不过没异步所占的份额大罢了。
Dispatcher内部维护了一个线程池,用来对Call网络请求,进行处理。每一个dispatcher可以最大并发64个请求,每一台主机可以最大并发5个。我们从下面代码可以看到:

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;

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

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  public Dispatcher() {
  }

  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,最大线程数为Integer.MAX_VALUE,线程存活时间为1分钟,容器为SynchronizeQueue的这样一个线程池。
关于SynchronizeQueue的介绍可以看这里SynchronizeQueue内容
他这个线程池很像四大线程池中的newCacheThreadPool。适合处理请求频繁,高并发的任务。
这里的线程池,默认是这样的,如果有需求可以自己设置的。
Dispatcher如他名字的含义一样,调度。进他的源码可以看到,它里面的方法就是,执行请求,取消请求,结束请求等之类的调用,他的类里面的代码不多,大家可以自己进去看看,很容易理解。这里提一嘴,建议在看这篇文章的同学,采取边看文章,边看源码的方式来理解掌握。


回过头来继续看这句代码:

client.dispatcher().enqueue(new AsyncCall(responseCallback));

我们知道他就是调用Dispatcher里面的enqueue方法,我们来看,Dispatcher里面的enqueue方法。

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

runningAsyncCalls是一个存储正在进行异步请求的容器,这边对它进行了判断,如果runningAsyncCalls的size小于最大并发量64,并且每台主机的正在请求量小于5(maxRequestsPerHost),runningAsyncCalls就把这个异步请求添加进去,接着用通过调用线程池中的线程去执行这个AsyncCall,否者就用readyAsyncCalls容器去存储该请求。这段逻辑很好理解。我们现在看一下AsyncCall。看看这个AsyncCall是什么。

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

这里我们发现了之前传过来的Callback回调,AsyncCall是RealCall的内部类。AsyncCall 继承NamedRunnable ,咦。。有Runnable,难到。。。我们接着看NamedRunnable

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

果然~~,NameRunnable是实现了Runnable接口。是一个抽象类,在run方法中执行了execute()方法,execute方法是一个抽象方法。他的实现在NameRunnable子类中,AsyncCall是NameRunnable的子类。所以,我们就定位到了AsyncCall类中的关键方法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 {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }

看到这里,我们要知道,之前Dispatcher里面的线程池执行的任务就是上面的execute()方法。然后根据上面的源码我们发现,在这里获取到了Response,网络响应结果。并且对根据条件,处理了对Callback回调的响应。

client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

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

            }
        });

之前我们在使用OKhttp进行一步请求的时候,传进去的Callback,结果回调,就是AsyncCall 里面execute()方法里面的Callback。
获取到Response的这行代码

Response response = getResponseWithInterceptorChain();

我们待会再看。我们接着看,发现finally语句块中执行的下面的语句:

client.dispatcher().finished(this);

调用了Dispatcher的finished方法。

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

这里的promoteCalls参数,在异步请求的情况下,Boolean值为true,同步的话,值为false。我们看到finished方法里面有一个同步代码块,首先他移除了当前正在运行的call(请求都完成了,到这该结束),逻辑没毛病。然后获取正在执行的请求数量,如果数量==0,idleCallback不为零,就执行idleCallback的run方法。idleCallback默认为null的,用户有需要得设置了才有。咦,我们刚刚是不是错过了什么逻辑。对了,这行代码还没看呢。没事咱现在看。
if (promoteCalls) promoteCalls();
我们分析的是异步请求,所以promoteCalls是true。我们看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.
    }
  }

逻辑很简单,我就不细说了,就是把readyAsyncCalls准备执行里的请求放到runningAsyncCalls正在执行的容器里,并通过executorService().execute(call);来执行。


接下来我们来看之前被我们放在一边的获取Response的方法,代码如下:

Response response = getResponseWithInterceptorChain();
Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());//注意1
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());//注意2
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

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

    return chain.proceed(originalRequest);
  }

这里我们见到了OKhttp里面的很重要一个接口Interceptor(拦截器)。

Interceptor(拦截器)

interceptor是什么?他是用来对发出去的网络请求和对应响应的responses 进行(Observes)观察,(modifies)修改和potentially short-circuits(可能的短路?掉线?不好翻译,理解吧),也就是对Request和Response进行修改,处理的一个借口。通常,interceptor(拦截器)会对Request和Response的Header进行add,remove,transform等操作。

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;
    ...
    ...
    省略...

我们现在来看上面的getResponseWithInterceptorChain()方法。他首先创建了一个ArrayList集合把一些xxxInterceptor添加进去,然后创建了RealInterceptorChain,并执行chain.proceed(originalRequest);方法。这里先说一个结论就是interceptors容器里面的各式各样的interceptor拦截器会,按添加的顺序,逐个执行intercept()方法。这个结论后续会被证明。然后我们上面代码,有两个注意点,那两个地方的interceptor是开发者,根据需要选择是否设置的。设置后执行顺序就是add的顺序。
接着我们看RealInterceptorChain.proceed(originalRequest);方法。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

这里,我删除了一些非关键代码,方便我们浏览。刚开始,index和calls的值都是0,我们看到他创建了一个RealInterceptorChain类型的next对象,他的构造方法这里注意一下,他的构造方法传进去的参数,有一个index+1,也就是说这个RealInterceptorChain的index值+1了。变成了1 注意这里是新创建的next对象里面的index值加一了,并不是说现在在所的对象里面index加一,Interceptor interceptor = interceptors.get(index);在执行该语句时,index还是0,这里如果开发者通过调用OKhttpClient的addInterceptor(Interceptor interceptor)方法,设置了拦截器,则会先执行用户通过该方法设置的拦截器,否则就是执行RetryAndFollowUpInterceptor 拦截器。这个拦截器看名字我们很容易知道,他是失败重试和重定向的时候发挥作用的,这里我们先不细说了。本篇文章的主要目的是捋清流程。我们接着看,这里我们没有add 自定义的拦截器,所以就执行了RetryAndFollowUpInterceptor 拦截器的interceptor.intercept(next);方法,注意这里把next对象,穿进去了,还有要记得我们之前强调的,next对象里面的index的值加一了已经,变成了1了。
然后我们看RetryAndFollowUpInterceptor 拦截器的interceptor.intercept(next);方法。

@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;//注意1
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

    streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
        call, eventListener, callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        response = realChain.proceed(request, streamAllocation, null, null);//注意2
        releaseConnection = false;

上面的代码,别的我们先不看,我们主要看我上面标注的注意1,和注意2处的代码。看注意1,这里传进来的chain就是next对象,然后注意2,那边realChain.proceed()方法,(⊙o⊙)哦~有点熟悉哦,我们进去看一下。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

发现又走了回来,注意,此时index的值不再是0了,此时变成了1,接着就是上面一样的逻辑,创建新的next对象,index加1,从interceptors容器中取出下一个interceptor拦截器,执行intercept(next)方法。正是这种递归式的调用,来吧interceptors集合里面的所有拦截器都跑一边,最后获取到Response对象。

总结

不自觉中说了这么多,之后的文章会对Interceptor的各个实现类,CacheControl等重要类进行剖析。最后上一张总结图:

这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_34902522/article/details/79483678