OkHttp 学习
发布日期:2021-05-06 19:09:52 浏览次数:38 分类:精选文章

本文共 10030 字,大约阅读时间需要 33 分钟。

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 协议。

 

官网

各个版本的差异:

谷歌再SDK6.0版本中取消了HttpClient类。

 

 

使用OkHttp

具体的使用见:,这个仓库的代码包含两部分,一部分是服务器端代码(java实现),另一部分代码是Android实现。

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

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

Callback() {} 回调函数

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

的函数。

 

常见类

 

 

 

 

 

 

主要函数

下面的解析,主要参考:

final class RealCall implements Call {    ......    //TODO 核心代码 开始真正的执行网络请求    Response getResponseWithInterceptorChain() throws IOException {    // Build a full stack of interceptors.    List
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 连接服务器 负责和服务器建立连接 这里才是真正的请求网络 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为例,说明拦截器

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设计的最优雅最核心的功能。

 

 

 

 

 

参考:

 

 

上一篇:RxJava学习
下一篇:设计模式--七大原则

发表评论

最新留言

很好
[***.229.124.182]2025年03月29日 00时15分07秒