OkHttp3源码解读(一)同步与异步

OkHttp3系列

OkHttp3源码解读(一)同步与异步

OkHttp3源码解读(二)请求响应与拦截器

OkHttp是一个很常用的网络请求库,支持HTTPS。缓存、异步/同步请求。协议类型是Http/1.0,Http/1.1,SPDY,Http/2.0,WebSocket。

Http/1.0 每一次请求创建一个TCP连接。Http/1.1 起,默认使用Keep-alive机制建立长连接,但如果客户端发送多个并行请求则必须建立多个TCP连接。Http/2.0 中多个并行请求在一个TCP上完成,还有请求压缩等特性。

网络传输使用的是封装的Socket,而不是URLConnection,数据读写使用的 NIO(Okio)。SPDY协议类似于HTTP,但旨在缩短网页的加载时间和提高安全性。SPDY协议通过压缩、多路复用和优先级来缩短加载时间。Retrofit也是基于okhttp,因此我们不能局限于只会使用。

OkHttp有如下特性:

1)支持HTTP2,对一台机器的所有请求共享同一个socket连接(同域名下所有通信都在单个连接上完成,消除了因多个连接而带来的延时和内存消耗)。

2)内置连接池,支持连接复用,减少请求延迟

3)透明的GZIP压缩减少响应数据的大小

4)通过缓存避免重复请求

5)失败时自动重连,自动重定向

本文解析的 OkHttp 版本是 3.9.0 !!!

本篇文章介绍OkHttp执行同步和异步请求的流程及区别,先看看大致流程图:

从上面的图我们可以看到,要进行同步或异步请求,要先准备好三个对象:OkHttpClient、Request、RealCall。然后执行同步方法execute()或异步方法 enqueue(),最终都是通过getResponseWithInterceptorChain()获取响应,下篇文章对getResponseWithInterceptorChain()进行讲解。

我们先从基本使用作为入口:

//  构建okHttpClient,相当于请求的客户端,Builder设计模式
OkHttpClient client = new OkHttpClient();
// 构建一个请求体,同样也是Builder设计模式
Request request = new Request.Builder()
                .url("http://www.baidu.com")
                .build();
//  生成一个Call对象(该对象是接口类型,后面会说), 然后调用execute执行,返回一个Response
Response response = client.newCall(request).execute();

以上就是一个简单的同步请求示例代码

1. 通过Builder模式创建OkHttpClient对象和Request对象

2. 调用OkHttpClient的newCall方法,获取一个Call对象,参数是Request

3. 调用execute方法获取一个Respone

同步请求流程:

我们先来看看 OkHttpClient 中的参数

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

• interceptors:拦截器,也是OkHttp的核心部分,后面下篇文章会将,链接在最后。

• Cache:请求缓存,只缓存 GET 请求。

• Dispatcher:分发器,下面会提及,主要是一个请求的控制器,维护三个请求队列,对请求进行管理和执行等。

• protocols:网络协议类,可以自己配置协议,但是必须包含 http/1.1,且不能包含 http/1.0,并且会默认移除 spdy/3.1,因为不会长久支持 spdy/3.1协议。

public enum Protocol {

  // 一种过时的且默认不支持持久socket(长连接)的明文帧
  HTTP_1_0("http/1.0"),

  // 一种包含长连接的明文帧
  HTTP_1_1("http/1.1"),

  /**
   * Chromium的二进制框架协议,包括头压缩、在同一个套接字上多路复用多个请求和服务器推送。OkHttp已经放弃了对 SPDY的支持,更倾向于 HTTP_2。
   */
  SPDY_3("spdy/3.1"),

  // IETF的二进制框架协议,包括头压缩、在同一个套接字上多路复用多个请求和服务器推送。
  HTTP_2("h2");
}

• connectionSpecs:指定HTTP流量通过的套接字连接的配置。

• eventListenerFactory:请求事件的侦听器。扩继承这个类来监视应用程序HTTP调用的数量、大小和持续时间。但在OkHttp 3.9,这个特性是不稳定的:API可能会改变。所有的start/connect/ acquisition事件最终都会接收到一个匹配的end/release事件,要么成功(非空参数),要么失败(非空可抛出)。
请求事件排序:requestHeaders→requestBody→responseHeaders→responseBody
所有事件方法都必须快速执行,不能有外部锁定,不能抛出异常。具体可另行查看。

• proxySelector:设置全局的代理,通过继承该类,设置具体代理的类型、地址和端口。

• cookieJar:向传出的HTTP请求添加cookie,收到的HTTP返回数据的cookie处理。

• socketFactory:顾名思义,创建socket的工厂类。

• hostnameVerifier:用于验证 URL 与主机名是否匹配。

• connectionPool:管理HTTP和HTTP/2连接的重用,以减少网络延迟。相同的地址的HTTP请求可能共享一个连接。该类实现了连接的策略,以便将来使用。

• connectTimeout :连接超时,默认10s,可以自己设置

• readTimeout :读取超时,同上;

• writeTimeout = 写入超时,同上;

。。。。。省略部分。。。。。还有一些重要的后面会讲到~~

Request

接下来Request对象有哪些属性

public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final @Nullable RequestBody body;
  final Object tag;

  private volatile CacheControl cacheControl; // Lazily initialized.
}

都是一些大家比较能理解的,这里就不赘述了。

同步请求中,在创建一个OkHttpClient对象和一个Request对象后,用 OkHttpClient 和 Request 创建一个RealCall对象(RealCall实现Call接口),接着调用RealCall的execute()方法,流程走的是红色线部分:

execute()方法实现:

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

先通过executed的值判断是否执行过,说明同一个Call对象只允许执行一次,执行多次则抛出异常。接着调用OkHttpClient的dispatcher()方法获得Dispatcher对象,并执行其executed(RealCall)方法。

Dispatcher类:

/**
 * Policy on when async requests are executed.
 *
 * <p>Each dispatcher uses an {@link ExecutorService} to run calls internally. If you supply your
 * own executor, it should be able to run {@linkplain #getMaxRequests the configured maximum} number
 * of calls concurrently.
 */
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() {
  }
  ...
}

Dispatcher类是执行 同步/异步 请求的控制类,内部有readyAsyncCalls、runningAsyncCalls、runningSyncCalls共三个队列。

①runningAsyncCalls用来存储正在执行的异步请求,有最大值maxRequest限制,默认64个;
②readyAsyncCalls用来存放等待的异步请求,也就是当runningAsyncCalls的尺寸达到maxRequest时,新的异步请求就会被放到readyAsyncCalls队列中等待;
③runningSyncCalls用来存储正在执行的同步请求。

Dispatcher内部有一个默认的ExecutorService类(继承自Executor),由它的实例化方法可以看出它其实是个线程池,用来执行异步请求,其默认参数如下:

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

线程池与Android下的Executors.newCachedThreadPool()比较类似,无任务上限,自动回收闲置60秒的线程,适用于大量耗时较短的任务,虽然无任务上限,但是Dispatcher对它进行了限制,最大异步请求数默认为64,同一时间同一个主机的最大请求数为5,也就是Dispatcher类中的maxRequests和maxRequestsPerHost变量。idleCallBack是Dispatcher中正在执行的同步和异步请求数量和为0时的回调。

接着我们继续看看他的execute方法:

  /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

Dispatcher的executed方法只是将RealCall对象添加到runningSyncCalls队列中。执行完Dispatcher的executed方法后,RealCall对象就调用getResponseWithInterceptorChain()方法执行请求,

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

我们可以看到,这里创建一个集合,然后放入很多拦截器对象,此处使用了责任链设计模式,在每个拦截器中都做了相应的处理。调用chain.proceed方法获取响应(下篇文章会讲)。

结束后调用dispatcher的finished(RealCall)方法:

  /** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

  /** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }

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

这里有两个finished方法,一个是同步请求(即Call接口的实现类RealCall对象)结束时调用的,一个是异步请求(即AsyncCall对象)结束时调用的,但最终都重载到一个finished方法,区别就是第三个参数promoteCalls不同,导致执行的行为不同。
这里我们是同步请求,在finished方法中,将当前请求从runningSyncCalls移除,移除失败则抛出异常,同步请求时promoteCalls参数为false,异步请求时为true,所以这里不执行promoteCalls()方法,然后计算当前执行的同步和异步请求之和为0且idleCallback不为空则执行回调。

异步请求流程

        //  构建okHttpClient,相当于请求的客户端,Builder设计模式
        OkHttpClient okHttpClient = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
        // 构建一个请求体,同样也是Builder设计模式
        Request request = new Request.Builder().url("http://www.baidu.com").build();
        //  生成一个Call对象,该对象是接口类型,后面会说
        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 {

                    }
                });

和同步请求的区别只有最后一步,异步请求调用的是enqueue(Callback),

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

同样请求只能执行一次,然后创建当前RealCall对象的内部类AsyncCall对象,调用Dispatcher的enqueue(AsyncCall)方法

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

如果当前异步请求数没超过maxRequests(默认64)且同一个主机请求数不超过maxRequestsPerHost(默认5),dispatcher将该AsyncCall请求添加到runningAsyncCalls队列,并交给线程池 executorService 执行,否则放入到readyAsyncCalls等待队列。

AsyncCall继承自NamedRunnable,NamedRunnable继承自Runnable,当线程池执行AsyncCall时,会执行NamedRunnable的run()方法,

public abstract class NamedRunnable implements Runnable {

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      // 调用抽象方法execute()
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

在NamedRunnable的run()方法中调用了抽象方法execute(),该方法由AsyncCall实现,所以会执行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);
      }
    }

还是调用getResponseWithInterceptorCahin()方法获取响应,最后调用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();
    }
   }

依然从runningAsyncCalls移除当前的异步请求Call,可以看到这里和同步请求时传入的参数不同,promoteCalls为true,执行promoteCalls()方法,然后判断当前正在执行的同步和异步请求之和是否为0,调用回调。看一下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.
    }
 }

如果当前异步请求数量已达到maxRequests,则返回;如果readyAsyncCalls为空,返回;否则,遍历readyAsyncCalls队列(等待队列),如果请求的同一主机的请求数不超过maxRequestsPerHost,则将其从readyAsyncCalls队列移除,加入到runningAsyncCalls队列,并执行请求。

同步请求和异步请求的大致流程就是这样,可以看到,同步请求只是将请求添加到队列,是由主线程进行控制的,一个接一个执行,异步请求是由dispatcher进行控制的,只要不超过限制就执行请求。

参考文章https://www.jianshu.com/p/65c423d6a8eb 

下篇文章对getResponseWithInterceptorChain()方法进行解读,其要点是拦截器,也是okhttp的核心!

OkHttp3源码解读(二)请求响应与拦截器

 

喜欢的话点个赞,是对我最大的支持~~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值