初略的看了Retrofit之后,OkHttp就是必不可少的了,Retrofit依赖了OkHttp,两个库都有一个Call
这么一个类,其意图也一样,只是所处的层次不同。
Retrofit依赖OkHttp来实现真正的Http网络请求。而OkHttp相对来是更加复杂的,本文先把其文档过一遍。
Overview
OkHttp
OkHttp是一个实现HTTP协议的客户端,其高效性源于:
- Http/2 若多个请求目标是一个主机,这几个请求可以共享一个Socket
- 若是Http/2不支持,即服务端不支持,用连接池的降低请求延迟
- 自带GZIP压缩数据
- 对于重复请求,自带一个Response缓存避免无意义请求
当网络不好时,OkHttp支持自动重试。能帮助处理一些常见的问题,
比如服务有多个IP地址,若第一个IP失败,会自动切换到后续的IP尝试,这对于多个数据源的服务端来说很有用。
支持现代 TLS 特性,(TLS 1.3, ALPN, certificate pinning)// todo 了解下
OkHttp是易用的,其request/response可以用构造者模式创建,且其对象是不可变的(immutability)。
支持同步异步调用。
补充,不可变性的优势有
不用担心并发,既然该对象不可变,那么多个线程访问时,就不存在不同步问题。 免除副作用的影响,一个对象不可变,那么其使用过程中始终是一样的,假设使用两次,第二次结果保证和第一次一样。
Get a URL
1 | OkHttpClient client = new OkHttpClient(); |
Post to a Server
发送http post请求时,携带实体数据body
http 格式包括 requestLine、header、body
1 | public static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); |
Requirements
要求Android 5.0+,Java 8+
依赖了 OKio 和 Kotlin标准库,两者向后兼容都是令人放心的。
推荐保持该库最新版本,和浏览器一样,保持最新的http客户端可提升安全性。本库会跟上TLS的更新。
OkHttp 会使用平台自带的TLS的实现等等。
Call
Http client要做的事很清晰,接受一个request,产生一个response,使用Call来表示。
Rewriting request 重写一个请求
用户在使用OkHttp时,是在站在比较高得层次思考需求得,我们只是简单得描述一个http请求,而okHttp会补充一些额外得细节,使得请求 更加有效率。
对于 origin request,okHttp可能会添加 Content-Length, TransferEncoding, User-Agent, Host, Connection, Content-Type。
若是没有注明 Accept-Encoding,会依据Response添加合适的。
若存在Cookie,也会添加。
对于一些请求会使用缓存好的Response. 当服务端支持了,If-Modified-Since If-None-Match头部信息,当客户端存在缓存时,会发送一个 get 请求来获知本地的缓存是否可用,客户端携带着我何时 获取过这个信息,服务端判断是否需要返回更新的数据,假设没有更新,将返回一个没有body的 response,从而提高效率。
Rewriting Response 重写一个响应
若是透明压缩开启,OkHttp将会丢弃Content-Encoding 和 Content-Length,因为对用户来说,其得到的时解压后的body,而header返回的是压缩的body, 故其长度是不对的。 (个人疑问?为什么不把解压后的长度值更新到header中,然后返回给用户?)
Follow-up Request 自动跟随request
重定向,转发的区别? 重定向会返回一个新的地址给客户端,让其重新请求,客户端有感知 转发,则在服务器内部转发该请求,客户端无感知
对于重定向请求,服务器返回响应码为 302 ,okHttp会帮助重新请求新的地址,返回给最终结果给用户。这样,用户对重定向也无感知了~~
对于需要验证身份的请求,(响应码为401,身份未验证),OkHttp会自动从 Authenticator(需要自己配置),获取对应的凭证来验证身份。
Retrying Request 请求重试
有时连接失败了,可能时因为连接断开了,或服务端不可达,OkHttp会自动尝试不同的路由。
由于,rewrite, redirect, follow-ups 和 retries的存在,一个请求可能会在中间过程会有很多个 request和 response,而 OkHttp 使用Call来封装 这些过程,用户感觉到还是只有一个输入一个输出。
Call执行有两种方式:
- 同步 sync: 调用线程阻塞等待知道结果可读。
- 异步 async: 调用线程只是将请求入队列,传入的callback会在结果可用时执行。
Call 可以被任意线程取消,只要该请求完成都可以取消。
注意,当某一个线程被取消时,此时在写入reqeustBody或读取responseBody的代码都会抛出IO异常,设计如此。
Dispatch 分发调度
对于异步请求,如何管理同时并发的请求是个问题。创建过多的连接会方法资源,过少会导致请求延迟较大。
如何平衡空间与时间的问题,使用Dispatcher来设置,默认的连接池大小的,默认为5,最大值为64。
其原理与线程池基本是一致的。
Caching
OkHttp 提供了一个可选的缓存模块,默认关闭。 参照一些实用的设计,比如市面的浏览器。
Basic Usage
1 | private val client: OkHttpClient = OkHttpClient.Builder() |
理想情况下,若是缓存命中,会跳过 DNS, 连接网络,下载数据包,
未命中,比如,未请求过该数据,或该数据不可缓存,依据返回来的时间判断缓存已经过期了。
Conditional Cache Hit, (条件命中?),需要请求网络来确定本地的缓存能够可用,通常是服务端返回一个304的状态告知缓存未修改,即依据客户端请求发来的 if-modified-since。
Connection
虽然用户只提供了一个URL来发送一个请求,但是OkHttp建立一个连接却需要三个要素: URL, Address, Route
URL
何为URL, Uniform Resource Locator,统一资源定位,比如一个 https://github.com/square/okhttp。
是抽象的
- 只是简单区分非加密http和加密https,却没有指定使用哪种加密算法。既没有要求如何验证对方的证书(HostNameVerify),也没说哪些证书可以信任(SSLSocketFactory)。
- 没有指明哪些代理服务应该使用,如何验证代理服务器。
也是具体的,一个URL指定了资源路径和查询参数,每个Web服务支持很多URL.
Address
地址指定了连接的是哪一个web服务,以及一些静态的配置,如端口,https的设置,偏好的协议(Http/2, SPDY)
SPDY是一个优化的Http协议,多路复用、请求优先级、报头压缩(http报头用的明文,这里用的是二进制)。不过,http/2推出后已经不建议使用了
~
同一个地址的URL可以共享一个tcp socket连接,低延迟,高吞吐。
若是服务端支持http/1.x,那么其连接池会复用。
还是好好了解http 1.x > 2.0的变化吧