简介
一个处理网络请求的开源项目,是安卓端最火热的轻量级框架,由移动支付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 协议。
官网
各个版本的差异:okhttp3与旧版本okhttp的区别分析
Android 上发送HTTP请求一般有两种方式,HttpClient和HttpUrlClient。
由于HttpClient存在的API数量过多,扩展困难等缺点,谷歌在SDK6.0版本中取消了HttpClient类。
Volley框架是谷歌官方给的网络请求的框架,见volley,在SDK6,0以后,去掉了HttpClient
使用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为例,说明拦截器
- 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)的特性,所以连接的重用总是比新建连接性能要好。
使用postman一次get请求,的抓包:
使用okhttp的方式,同样的一次get请求:
使用OKHTTP复用,两次get请求(先后间隔5秒),保证前后两次请求的 OkHttpClient 对象是同一个。
可以看出,在第14秒的时候,第一次请求,在第21秒的时候,第二次请求。
重连机制---RetryAndFollowUpInterceptor
有时为了安全起见,服务提供方会提供多个http地址,这样如果我们请求某个ip出现异常,可以重试其他的ip地址,来尽量保证系统的稳定,
参考:
https://www.jianshu.com/p/0b7cd90bec2f