OkHttp

简介

一个处理网络请求的开源项目,是安卓端最火热的轻量级框架,由移动支付Square公司贡献(该公司还贡献了Picasso)
用于替代HttpUrlConnection和Apache HttpClient(android API23 6.0里已移除HttpClient,现在已经打不出来)

在现代应用程序网络处理中,HTTP是我们交换数据和媒体的方式。高效地执行HTTP可以使您的数据加载更快并节省带宽。

在默认情况下,OKHTTP是高效的HTTP客户端:
(1)HTTP/2支持允许对同一主机的所有请求共享一个套接字。
(2)连接池减少了请求延迟(如果HTTP/2不可用)。
(3)透明gzip缩小下载大小。
(4)响应缓存完全避免了重复请求的网络。

当网络出现问题时,OKHTTP会坚持下去:它会自动从常见的连接问题中恢复。如果您的服务有多个IP地址,在第一次连接失败时,OKHTTP将尝试备用地址。这对于IPv4+IPv6和托管在冗余数据中心中的服务是必需的。OKHTTP支持现代TLS功能(TLS 1.3、ALPN、证书固定)。它可以配置为回调以实现广泛的连接。

使用OKHTTP很容易。它的request/response API设计为具有流畅的构建者模式和不变性。它支持同步阻塞调用和带回调的异步调用。

OKHTTP支持Android 5 +(API级别21 +)和Java 8 +。

 

学前必备知识

需要了解 TCP 协议, 和 HTTP 协议。

 

官网

OkHttp

各个版本的差异:okhttp3与旧版本okhttp的区别分析

Android 上发送HTTP请求一般有两种方式,HttpClient和HttpUrlClient。

由于HttpClient存在的API数量过多,扩展困难等缺点,谷歌在SDK6.0版本中取消了HttpClient类。

 

Volley框架是谷歌官方给的网络请求的框架,见volley,在SDK6,0以后,去掉了HttpClient

面试常客:OkHttp和Volley的有什么区别?

 

 

使用OkHttp

具体的使用见:https://github.com/gong-shuang/MyOkHttp,这个仓库的代码包含两部分,一部分是服务器端代码(java实现),另一部分代码是Android实现。

在理解了HTTP的协议后,先抽象一下,从发送“请求包”到接受到服务器“响应包”的过程。

                                                               一次HTTP请求过程
步骤 抽象 具体 对应okhttp
1 创建发送包的对象 构造结构体(new) 创建OkHttpClient对象
2 组包(向包中填充数据) 填充请求头和数据 构造发包,获取call对象
3 发送 丢到发送队列中,由专门的线程负责发送 同步,或者异步
4 等待接受 由系统底层接口将响应包丢到接受队列中,
5 处理响应包 收到响应包后,解析响应头和数据

Callback() {} 回调函数

中设置,请求成功或者失败

的函数。

 

常见类

线程池:
public synchronized ExecutorService executorService() {
  if (executorService == null) {
    executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
        new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
  }
  return executorService;
}


该线程池的核心线程数为 0,线程池最有能容纳 Integer.MAX_VALUE 个线程,且线程的空闲存活时间为 60s(可以理解为 okhttp 随时可以创建新的线程来满足需要。可以保证网络的 I/O 任务有线程来处理,不被阻塞)。

重点是使用的阻塞队列为 SynchronousQueue,

关于阻塞队列:SynchronousQueue详解

 

主要函数

下面的解析,主要参考:彻底理解OkHttp - OkHttp 源码解析及OkHttp的设计思想

final class RealCall implements Call {

    ......

    //TODO 核心代码 开始真正的执行网络请求
    Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();

    //TODO 在配置okhttpClient 时设置的intercept 由用户自己设置
    interceptors.addAll(client.interceptors());

    //TODO 负责处理失败后的重试与重定向
    interceptors.add(new RetryAndFollowUpInterceptor(client));

    //TODO 负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应 处理 配置请求头等信息
    //TODO 从应用程序代码到网络代码的桥梁。首先,它根据用户请求构建网络请求。然后它继续呼叫网络。最后,它根据网络响应构建用户响应。
    interceptors.add(new BridgeInterceptor(client.cookieJar()));

    //TODO 处理 缓存配置 根据条件(存在响应缓存并被设置为不变的或者响应在有效期内)返回缓存响应
    //TODO 设置请求头(If-None-Match、If-Modified-Since等) 服务器可能返回304(未修改)
    //TODO 可配置用户自己设置的缓存拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));

    //TODO 连接服务器 负责和服务器建立连接 这里才是真正的请求网络
    //  复用连接池中的连接,如果没有就与服务器建立新的socket连接。
    interceptors.add(new ConnectInterceptor(client));

    if (!forWebSocket) {
      //TODO 配置okhttpClient 时设置的networkInterceptors
      //TODO 返回观察单个网络请求和响应的不可变拦截器列表。
      interceptors.addAll(client.networkInterceptors());
    }

    //TODO 执行流操作(写出请求体、获得响应数据) 负责向服务器发送请求数据、从服务器读取响应数据
    //TODO 进行http请求报文的封装与请求报文的解析
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //TODO 创建责任链
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      //TODO 执行责任链
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

  ......

}

从上述代码中,可以看出都实现了Interceptor接口,这是okhttp最核心的部分,采用责任链的模式来使每个功能分开,每个Interceptor自行完成自己的任务,并且将不属于自己的任务交给下一个,简化了各自的责任和逻辑。
我们着重分析一下,okhttp的设计实现,如何通过责任链来进行传递返回数据的。
上述代码中可以看出interceptors,是传递到了RealInterceptorChain该类实现了Interceptor.Chain,并且执行了chain.proceed(originalRequest)。
其实核心代码就是chain.proceed() 通过该方法进行责任链的执行。

public final class RealInterceptorChain implements Interceptor.Chain {

  ....
  
  
  public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.exchange != null && !this.exchange.connection().supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.exchange != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    //TODO 创建新的拦截链,链中的拦截器集合index+1
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
    //TODO 执行当前的拦截器-如果在配置okhttpClient,时没有设置intercept默认是先执行:retryAndFollowUpInterceptor 拦截器
    Interceptor interceptor = interceptors.get(index);
    //TODO 执行拦截器
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (exchange != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

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

从上述代码,我们可以知道,新建了一个RealInterceptorChain 责任链 并且 index+1,然后 执行interceptors.get(index); 返回Response。

这样设计的一个好处就是,责任链中每个拦截器都会执行chain.proceed()方法之前的代码,等责任链最后一个拦截器执行完毕后会返回最终的响应数据,而chain.proceed() 方法会得到最终的响应数据,这时就会执行每个拦截器的chain.proceed()方法之后的代码,其实就是对响应数据的一些操作。

 

以cache为例,说明拦截器

5分钟看懂系列:HTTP缓存机制详解

  1. OkHttp只有配置了cache才会启用缓存,且只支持Get的缓存。
public final class CacheInterceptor implements Interceptor {

    ....

    @Override 
    public Response intercept(Chain chain) throws IOException {
    //TODO 获取request对应缓存的Response 如果用户没有配置缓存拦截器 cacheCandidate == null
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    //TODO 执行响应缓存策略
    long now = System.currentTimeMillis();
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    //TODO 如果networkRequest == null 则说明不使用网络请求
    Request networkRequest = strategy.networkRequest;
    //TODO 获取缓存中(CacheStrategy)的Response
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }

    //TODO 缓存无效 关闭资源
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    //TODO networkRequest == null 不使用网路请求 且没有缓存 cacheResponse == null  返回失败
    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    //TODO 不使用网络请求 且存在缓存 直接返回响应
    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

//上部分代码,是在没有网络的时候的处理。
//那么下部分代码,是有网络的时候处理。

    //TODO 执行下一个拦截器
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    //TODO 网络请求 回来 更新缓存
    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      //TODO 304响应码 自从上次请求后,请求需要响应的内容未发生改变
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    //TODO 缓存Response
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }
  
  ....
  
}

上面,上半部分的代码做的几件事。
(1)如果用户自己配置了缓存拦截器,cacheCandidate = cache.Response 获取用户自己存储的Response,否则 cacheCandidate = null;同时从CacheStrategy 获取cacheResponse 和 networkRequest。(这部分属于初始化)
(2)如果cacheCandidate != null 而 cacheResponse == null 说明缓存无效清除cacheCandidate缓存。(意思是,用户自己设置了缓存,但是却没有缓存)
(3)如果networkRequest == null 说明没有网络,cacheResponse == null 没有缓存,返回失败的信息,责任链此时也就终止,不会在往下继续执行。
(4)如果networkRequest == null 说明没有网络,cacheResponse != null 有缓存,返回缓存的信息,责任链此时也就终止,不会在往下继续执行。
下半部分(有网络时)的代码主要做了这几件事:
(1)执行下一个拦截器,也就是请求网络
(2)责任链执行完毕后,会返回最终响应数据,如果缓存存在则更新缓存,如果缓存不存在就加入到缓存中去。

这样就体现出了,责任链这样实现的好处了,当责任链执行完毕,如果拦截器想要拿到最终的数据做其他的逻辑处理等,这样就不用在做其他的调用方法逻辑了,直接在当前的拦截器就可以拿到最终的数据。
这也是okhttp设计的最优雅最核心的功能。

 

 

 

 

 

多路复用----ConnectInterceptor

 

多路复用的意思是:

HTTP是建立在TCP协议之上,HTTP协议的瓶颈及其优化技巧都是基于TCP协议本身的特性。比如TCP建立连接时也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的建立连接,但是这些连接无法复用会导致每次请求都经历三次握手和慢启动。
正是由于TCP在建立连接的初期有慢启动(slow start)的特性,所以连接的重用总是比新建连接性能要好

HTTP/2 多路复用技术分享

使用postman一次get请求,的抓包:

OkHttp 学习_okhttp

OkHttp 学习_okhttp_02

使用okhttp的方式,同样的一次get请求:

OkHttp 学习_拦截器_03

使用OKHTTP复用,两次get请求(先后间隔5秒),保证前后两次请求的 OkHttpClient 对象是同一个。

可以看出,在第14秒的时候,第一次请求,在第21秒的时候,第二次请求。

OkHttp 学习_缓存_04

 

 

重连机制---RetryAndFollowUpInterceptor

有时为了安全起见,服务提供方会提供多个http地址,这样如果我们请求某个ip出现异常,可以重试其他的ip地址,来尽量保证系统的稳定

 

 

参考:

https://www.jianshu.com/p/0b7cd90bec2f

OkHttp源码解析——HTTP请求的逻辑流程