
本文共 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. Listinterceptors = 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设计的最优雅最核心的功能。
参考:
发表评论
最新留言
关于作者
