okhttp内部总结分析

okhttp3总体流程图:

webp

liucheng


先来回顾一下代码的使用流程,然后跟着流程一步步来分析:

 

1.1 创建 OkHttpClient 对象

OkHttpClient client = new OkHttpClient();

其实okHttpClient用的也是builder构建者模式:

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

实际上,OkHttpClient.Builder 类成员很多,用来设置连接的各种参数,官方建议使用单例模式构建一个client。 直接new只是使用了默认配置:

public Builder() {
  dispatcher = new Dispatcher();
  protocols = DEFAULT_PROTOCOLS;
  connectionSpecs = DEFAULT_CONNECTION_SPECS;
  proxySelector = ProxySelector.getDefault();
  cookieJar = CookieJar.NO_COOKIES;
  socketFactory = SocketFactory.getDefault();
  hostnameVerifier = OkHostnameVerifier.INSTANCE;
  certificatePinner = CertificatePinner.DEFAULT;
  proxyAuthenticator = Authenticator.NONE;
  authenticator = Authenticator.NONE;
  connectionPool = new ConnectionPool();
  dns = Dns.SYSTEM;
  followSslRedirects = true;
  followRedirects = true;
  retryOnConnectionFailure = true;
  connectTimeout = 10_000;
  readTimeout = 10_000;
  writeTimeout = 10_000;
}

1.2 创建Request请求对象

Request request = new Request.Builder()
  .url(url)
  .build();

1.3 创建Call对象

OkHttpClient 实现了Call.Factory,负责根据请求创建新的 Call。

callFactory 负责创建 HTTP 请求,HTTP 请求被抽象为了 okhttp3.Call 类,它表示一个已经准备好,可以随时执行的 HTTP 请求

Call call = client.newCall(request);

1.4 同步阻塞执行请求

Response response = call.execute();

1.5 异步回调执行请求

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

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        System.out.println(response.body().string());
}
});

2. 精妙的线程池

2.1 同步请求时

实际上newCall方法会返回一个Realcall对象,而它同步执行execute()方法时:

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    try {
        //仅仅标记作用
      client.dispatcher().executed(this);
      //这才是真正的执行下一步发请求,同步异步都会走到这个方法
      Response result = getResponseWithInterceptorChain(false);
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

虽然dispatcher也掺和了,其实没什么用,涉及到 dispatcher 的内容只不过是在内部做了个标记,表明有个有个任务正在执行:

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

2.2 异步请求

2.2.1 反向代理模型

服务器会添加Header并自动转发请求给后端集群,接着返回数据结果给用户。可以提高服务的负载均衡能力,实现非阻塞、高可用、高并发连接,避免资源全部放到一台服务器而带来的负载。

 

webp

fxdl

而在OkHttp中,非常类似于上述场景,它使用Dispatcher作为任务的派发器,线程池对应多台后置服务器,用AsyncCall对应Socket请求,用Deque<readyAsyncCalls>对应Nginx的内部缓存。

webp

xcc

具体成员如下

  • maxRequests = 64: 最大并发请求数为64
  • maxRequestsPerHost = 5: 每个主机最大请求数为5
  • Dispatcher: 分发者,也就是生产者(默认在主线程)
  • AsyncCall: 队列中需要处理的Runnable(包装了异步回调接口)
  • ExecutorService:消费者池(也就是线程池)
  • Deque<readyAsyncCalls>:缓存(用数组实现,可自动扩容,无大小限制)
  • Deque<runningAsyncCalls>:正在运行的任务,仅仅是用来引用正在运行的任务以判断并发量,注意它并不是消费者缓存

通过将请求任务分发给多个线程,可以显著的减少I/O等待时间。

2.2.2 异步执行过程

具体过程如下:

  1. enqueue()方法调用后,判断runningAsyncCalls队列是否还有空位子。
  2. 若有空位,那就调用executorService().execute(call);交给线程池执行请求,并且添加进运行队列
  3. 啊,没空位了,只能进readyAsyncCalls等待队列了……

看一下call任务内部的执行方法:

…………
//果然和同步方式一样,最后通过这个方法去发送给下一步。
Response response = getResponseWithInterceptorChain(forWebSocket);

…………
finally {
  //最关键的代码
client.dispatcher().finished(this);
}   

当任务执行完成后,无论是否有异常,finally代码段总会被执行,也就是会调用Dispatcher的finished函数,打开源码,发现它将正在运行的任务Call从队列runningAsyncCalls中移除后,接着执行promoteCalls()函数

if (runningCallsForHost(call) < maxRequestsPerHost) {
        //将缓存等待区最后一个移动到运行区中,并执行
      i.remove();
      runningAsyncCalls.add(call);
      executorService().execute(call);
   }

实际上就是会在任务执行完的最后,把自己从运行队列移除,唤醒一个等待队列的任务,让他去执行。
这样,就主动的把缓存队列向前走了一步,而没有使用互斥锁等复杂编码。

2.2.3 异步总结

  • OkHttp采用Dispatcher技术,反向代理,优化了单生产者多消费者模式,与线程池配合实现了高并发,低阻塞的运行
  • Okhttp采用Deque作为缓存,按照入队的顺序先进先出
  • OkHttp最出彩的地方就是在try/finally中调用了finished函数,可以在任务结束时候唤醒等待的任务,主动控制等待队列的移动,而不是采用锁或者wait/notify,极大减少了编码复杂性

3. 工作的核心----拦截器

3.1 拦截器的介绍与种类

不管同步异步发送任务请求,最后都会执行getResponseWithInterceptorChain:

private 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(
      retryAndFollowUpInterceptor.isForWebSocket()));

  Interceptor.Chain chain = new RealInterceptorChain(
      interceptors, null, null, null, 0, originalRequest);
  return chain.proceed(originalRequest);
}

拦截器是个啥?官方介绍和图片:

the whole thing is just a stack of built-in interceptors.

 

webp

ljq2

可见 Interceptor 是 OkHttp 最核心的一个东西,不要误以为它只负责拦截请求进行一些额外的处理(例如 cookie),实际上它把实际的网络请求、缓存、透明压缩等功能都统一了起来,每一个功能都只是一个 Interceptor,它们再连接成一个 Interceptor.Chain,环环相扣,最终圆满完成一次网络请求。

从 getResponseWithInterceptorChain 函数我们可以看到,Interceptor.Chain 的分布依次是:

webp

ljq

 

  1. 在配置 OkHttpClient 时设置的 interceptors
  2. 负责失败重试以及重定向的 RetryAndFollowUpInterceptor
  3. 负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor
  4. 负责读取缓存直接返回、更新缓存的 CacheInterceptor
  5. 负责和服务器建立连接的 ConnectInterceptor
  6. 配置 OkHttpClient 时设置的 networkInterceptors
  7. 负责向服务器发送请求数据、从服务器读取响应数据的CallServerInterceptor

3.2 拦截器的责任链模式

实际上,是责任链模式的最佳应用(如同事件分发机制),每个拦截器可以自己拦截处理,或者交给下一个拦截器,让每个 Interceptor 自行决定能否完成任务以及怎么完成任务。

其实 Interceptor 的设计也是一种分层的思想,每个 Interceptor 就是一层。为什么要套这么多层呢?分层的思想在 TCP/IP 协议中就体现得淋漓尽致,分层简化了每一层的逻辑,每层只需要关注自己的责任(单一原则思想也在此体现),而各层之间通过约定的接口/协议进行合作(面向接口编程思想),共同完成复杂的任务。

4 总结

  1. OkHttpClient 实现 Call.Factory,负责为 Request 创建 Call;
  2. RealCall 为具体的 Call 实现,其 enqueue() 异步接口通过 Dispatcher 利用 ExecutorService 实现,而最终进行网络请求时和同步 execute() 接口一致,都是通过 getResponseWithInterceptorChain() 函数实现;
  3. getResponseWithInterceptorChain() 中利用 Interceptor 链条,分层实现缓存、透明压缩、网络 IO 等功能;

转载于:https://my.oschina.net/simaben/blog/3048684

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值