阅读 100

OkHttp3.0解析 —— 从源码的角度谈谈发起网络请求时做的操作

OkHttp是square公司出品的一款网络加载框架,我们今天从源码的角度来看看,他在我们进行同步和异步请求的时候,内部都具体做了什么操作。

使用

在使用OkHttp的时候,首先第一步是实例化一个OkHttpClient对象。

OkHttpClient okHttpClient = new OkHttpClient();
复制代码

我们来看看他的构造方法。

public OkHttpClient() {
    this(new Builder());
  }
 
  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);
    this.eventListenerFactory = builder.eventListenerFactory;
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;
    this.cache = builder.cache;
    this.internalCache = builder.internalCache;
    this.socketFactory = builder.socketFactory;
 
    boolean isTLS = false;
    for (ConnectionSpec spec : connectionSpecs) {
      isTLS = isTLS || spec.isTls();
    }
 
    if (builder.sslSocketFactory != null || !isTLS) {
      this.sslSocketFactory = builder.sslSocketFactory;
      this.certificateChainCleaner = builder.certificateChainCleaner;
    } else {
      X509TrustManager trustManager = Util.platformTrustManager();
      this.sslSocketFactory = newSslSocketFactory(trustManager);
      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
    }
 
    if (sslSocketFactory != null) {
      Platform.get().configureSslSocketFactory(sslSocketFactory);
    }
 
    this.hostnameVerifier = builder.hostnameVerifier;
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
        certificateChainCleaner);
    this.proxyAuthenticator = builder.proxyAuthenticator;
    this.authenticator = builder.authenticator;
    this.connectionPool = builder.connectionPool;
    this.dns = builder.dns;
    this.followSslRedirects = builder.followSslRedirects;
    this.followRedirects = builder.followRedirects;
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    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);
    }
  }

复制代码

可以看到,其构造方法里面的参数非常非常多,包括于对socket证书校验、设置读取和写入的超时时间、设置拦截器、连接池的操作等等。可以说如果我们想使用OkHttp内部默认的设置,那么我们可以直接new一个OkHttpClient就可以,但是如果我们想按照自己的方法来设置一些参数,那么我们可以使用Builder来处理。

发起网络请求

        final String URL = "";
 
        OkHttpClient okHttpClient = new OkHttpClient();
 
        Request request = new Request.Builder()
                .url(URL)
                .get()
                .build();
 
        try {
            Response response = okHttpClient.newCall(request).execute();
            String result = response.body().string();
        } catch (IOException e) {
            e.printStackTrace();
        }

复制代码

我们可以看到,一般的网络请求分为三步。第一为实例化一个OKHttpClient,第二步实例化一个Request,第三步通过execute()发起同步的网络请求。我们逐步来分析。由于刚刚已经分析过了okHttpClient,所以这里不再分析,我们从request开始。

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里面我们可以看到里面的构造方法,其实里面的功能并不多,构造方法里面的参数决定了其功能。看到,Reuqest的主要功能就几个,第一为url,也就是请求的地址。第二为method,请求的方式(GET请求或者POST请求)。headers请求头,body为请求体。最后有一个tags,这个是干嘛的呐?其实这个tags是在给一个网络请求做标记,如果我们在进行网络请求的时候出了什么问题,需要取消掉该网络请求,那么就在发起网络请求的时候用tags先标记下,然后根据标记来取消网络请求。

newCall

我们点进newCall里面看看。

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

复制代码

可以看到在newCall内部其实用的是newRealCall方法,等于说所有的网络请求操作都是在newRealCall里面进行的操作。那么就很好办了,我们直接通过newRealCall来看看,内部进行的同步操作和异步操作的过程是怎么样的。

同步请求

 @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);
      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);
    }
  }

复制代码

我们从源码里面可以看到,一个同步的网络请求总共做了四件事。

1.if判断,检查这个executed是否正在执行,如果执行就直接抛出异常。这里需要说明一点,一个网络请求的操作,不能同时被操作两次,如果操作两次,则报错。

2.通过分发器(又可以叫调度器)dispatcher来执行同步任务操作。关于这个dispatcher这里要说明一下,很多情况下,其实这个调度器是在异步操作里面才会用到,其实在同步里面也做了操作。关于dispatcher有机会我专门写一篇文章来讲解。

3.通过getResonseWithInterceptorChain()将结果返回。

4.通过dispatcher结束掉任务finished()。

其实通过上面几步,我们还是没有看出来网络请求到底如何发起的,那么到底是在哪里做了操作网络请求的呐?答案就在第三步getResonseWithInterceptorChain()。我们看下这个源码。

getResonseWithInterceptorChain

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    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());
    }
    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的核心了。在这里,首先会new一个拦截器的集合,之后把我们自己定义的拦截器和okhttp自带的拦截器一起放入到这个集合当中去,最后放入到一个叫做拦截器链RealInterceptorChain里面,通过proceed发起请求,从而实现网络请求操作。说道拦截器,这里需要带一笔,拦截器可以说是okhttp的精华核心部分,在我看来,okhttp之所以牛逼就是牛逼在这些拦截器上面。我们可以通过不同的需求添加和改造不同的拦截器,并且这些拦截器之间的耦合度非常的低,等于说在改造一种拦截器之后,并不会对另外的拦截器有任何的影响。可以说降低耦合度是写代码的最高境界,这相比较volley不知道强了多少倍。

我们可以看到,在拦截器这里使用了责任链的设计模式。他通过一个一个的拦截器,根据添加顺序来环环相扣,执行完一个拦截之后再执行另外一个,最后把他们凑成一个环chain,放入到RealInterceptorChain当中去。我们再来看下RealInterceptorChain方法里面操作。

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final RealConnection connection;
  private final int index;
  private final Request request;
  private int calls;
 
  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
                              HttpCodec httpCodec, RealConnection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
  }
 
  @Override public Connection connection() {
    return connection;
  }
 
  public StreamAllocation streamAllocation() {
    return streamAllocation;
  }
 
  public HttpCodec httpStream() {
    return httpCodec;
  }
 
  @Override public Request request() {
    return request;
  }
 
  @Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }
 
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
 
    ......
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    
    ...... 
 
    return response;
  }
}

复制代码

从源码可以看到,其实在RealInterceptorChain方法中除了赋值以外其他并没有做什么特别的操作。那么我们可以知道,主要的操作其实是在这个类的proceed方法里面做了操作。在proceed的方法里面我们可以看到,做了两件事。第一,创建下一个拦截器,并且把i(ndex + 1)传入里面。第二,通过index获取到拦截器,并且将下一个拦截器链放入到这个拦截器里面。由此可以看到,拦截器的执行是层层递进,每次都是执行下一个拦截器,由下一个拦截器来完成方法操作。由网上找来的一张图我们可以看到这里面的具体操作。

图片

异步操作

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

复制代码

在说明异步操作的时候,我们需要先了解一下任务队列的概念。在okhttp中有三种任务队列和一种线程池。

readyAsyncCalls: 待执行异步任务队列

runningAsyncCalls: 运行中异步任务队列

runningSyncCalls: 运行中同步任务队列

executorService: 任务队列线程池

当可以执行异步任务的列队数大于线程中可以请求的最大请求数量时,则证明线程池比较繁忙,不能再往里面添加任务,所以先把他放到等待异步执行的任务队列当中去,等到线程池有空间执行再把他取出来。如果线程池有空间,则直接将call任务放到运行中异步任务队列中去,然后通过exectorService线程池来启动执行。异步操作其实本质上和同步操作类似,只是多了这么个等待和运行的概念而已。

下面通过一张图来展示okhttp整体的发起请求的流程,到此okhttp发起网络请求部分完成。

image

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