OkHttp3的网络编程
简介
如今在大部分的Android应用中都大抵使用OkHttp3作为网络请求的框架,无论是纯使用okhttp进行请求还是在Retrofit2中都能看到okhttp的影子。先简单介绍一下OkHtto的特点:
- HTTP/2 支持所有的请求能够在同一个host下复用同一个Socket;
- 连接池降低了请求的延长(非 HTTP/2);
- 支持 GZIP 传输压缩;
- 响应缓存避免重复的请求;
除此以外,当OkHttp在访问一个IP地址失败时会自动尝试下一个IP(前提是你有配置多个ip地址),还处理了代理服务器问题和SSL握手失败问题。
简单使用:1
2
3
4
5
6
7
8
9
10
11OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
详情介绍请见官方教程:OkHttp
上面的一切都可以从源代码中可以看到如何实现,现在我们就进入代码阅读。
原理
Okhttp不同于 Volley
那样是基于HttpURLConnection
和HttpClient
基础上实现的一套机制而是自己通过TCP实现的完整一套网络机制,以便更高效的完成Http请求。
OKHttpClient
一切从 OkHttpClient
开始,OkHttpClient
通过 Builder 传入或初始化整个框架所需要的参数和配置,比如请求分发器Dispatcher
,拦截器Interceptor
等,这些参数和配置都可以借由 OkHttpClient
这个角色被子模块快速的调用,比如 dispatcher,call,cache等等。同时 OkHttpClient
也负责将 Request 转化成实际请求Call方便Dispatcher
进行分发。
Dispatcher
如在最上面的代码所示,在发起请求时需要一个Request对象来封装每次的请求参数,而这个Request对象会在 OkHttpClient
中转换为真正的请求对象Call并将其放入Dispatcher中进行分发,以同步或者异步的方式。
同步
Dispatcher
会在同步执行任务队列中 runningSyncCalls
记录当前被执行过得任务Call,同时在当前线程中去执行Call的getResponseWithInterceptorChain()
方法,直接获取当前的返回数据Response,完成后会将call从 runningSyncCalls
队列中移除。
getResponseWithInterceptorChain 方法中包含着之后要说的拦截器和链式调用机制。
异步
在异步中 Dispatcher
又维护了两个队列,runningAsyncCalls
和readyAsyncCalls
,前者为正在请求的队列,后者是当请求队列超过一定大小后的准备队列。
Dispatcher内部实现了懒加载无边界限制的线程池方式,同时该线程池采用了 SynchronousQueue这种阻塞队列。1
2
3
4
5
6
7public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
这种方式能够快速的传递元素,适用于高频繁请求的场景。
异步执行是通过 Call.enqueue(Callback responseCallback)
来执行,在 Dispatcher
中添加一个封装了Callback的Call的匿名内部类Runnable来执行当前的Call。这里的call在调用会转换成其内部类AsyncCall
,AsyncCall的execute方法最后还是会回调到Call的 getResponseWithInterceptorChain方法来完成请求,同时将返回数据或者状态通过Callback来完成。
Interceptor拦截链
通过 Dispatcher
已经知道了OkHttp是怎样分发请求的,那么下一步也就是最重要的是OkHttp是怎么完成请求的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
OkHttp3对HTTP请求是通过Interceptor链来处理的。拦截器接口中有 intercept(Chain chain)
方法,同时返回Response,主要功能是针对Request和Response的切面处理。
在RealInterceptorChain
的初始化中传入了整个拦截器列表,而在RealInterceptorChain
中维护了拦截器运行的迭代数,保证每次在拦截器中的intercept(Chain chain)
中调用 chain.proceed()
都能顺序的执行下一个拦截器。
用户自定义 Interceptor
用户自定义的Interceptor
分为应用Interceptor
和网络NetworkInterceptor
,其实两者并没有什么本质的区别,只不过所处的位置不同。
应用Interceptor
位于拦截链的首位,不会担心响应和重定向之间的中间响应,通常只调用一次,即使HTTP响应式通过缓存提供的。
而网络NetworkInterceptor
位于 ConnectInterceptor
与CallServerInterceptor
之间,可以在数据传递到网络时即时观测,能够在重定向和重试中操作中间响应。
RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor
首先创建了StreamAllocation
对象,借由 RealInterceptorChain
将其传递给后面执行的Interceptor。之后便是重要的两部分,连接失败重试(Retry)和继续发起请求(Follow up)两部分。
在后面的拦截器的请求失败,会抛出两种异常
RouteException
这个异常发生在 Request 请求还没有发出去前,就是打开 Socket 连接失败。这个异常是 OkHttp 自定义的异常,是一个包裹类,包裹住了建联失败中发生的各种 Exception 主要发生ConnectInterceptor
建立连接环节,比如连接超时抛出的SocketTimeoutException
,包裹在RouteException
中IOException
这个异常发生在 Request 请求发出并且读取 Response 响应的过程中,TCP 已经连接,或者 TLS 已经成功握手后,连接资源准备完毕。主要发生在CallServerInterceptor
中,通过建立好的通道,发送请求并且读取响应的环节。比如读取超时抛出的SocketTimeoutException
那么针对以上的异常,OkHttp 使用 recover 方法判断是否可以重试。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/**
* Report and attempt to recover from a failure to communicate with a server. Returns true if
* {@code e} is recoverable, or false if the failure is permanent. Requests with a body can only
* be recovered if the body is buffered or if the failure occurred before the request has been
* sent.
*/
private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;
// We can't send the request body again.
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
// This exception is fatal.
if (!isRecoverable(e, requestSendStarted)) return false;
// No more routes to attempt.
if (!streamAllocation.hasMoreRoutes()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
简单的描述一下四个条件:
- 在 OkHttpClient是否配置了
retryOnConnectionFailure(true)
- 不是建立连接阶段发生的异常,同时请求的内容不是可重复发送
- 使用
isRecoverable
过滤不可恢复异常,如ProtocolException
,InterruptedIOException
,CertificateException
和SSLPeerUnverifiedException
。 - 已经没有其他的路由可以使用。
RouteSelector
封装了选择可用路由进行连接的策略。
如果连接成功,即获得了 Response 响应,但不一定是 200 OK,还有一些其他情况。比如 3xx 重定向,401 未授权等,这些响应码是允许我们再次发起请求的。比如重定向,获取目标地址后,再次发起请求。又比如 401 未授权,可以在 Request 中新增头部 “Authorization” 授权信息再次发起请求等。这时候就需要 followUpRequest
对response的响应码进行判断,来确定是否支持再次发起请求。
- 未授权(407的代理为授权,401的未授权)—— 在请求中添加 “Proxy-Authorization” 和 “Authorization”
- 重定向 (307和308 —— 要求为get和head请求方式)(300,301,302,303) —— 拿到重定向的URL重建Request
- 超时 (408 客户端超时) —— 部分服务器会因为客户端请求时间太长而返回 408,此时如果请求体没有实现标记接口 UnrepeatableRequestBody, OkHttp 会再把之前的请求没有修改重新发出
BridgeInterceptor
BridgeInterceptor
用来添加 http 的header,主要修改目标为 request。主要内容:
- 在header中添加一些常用的字段,如 Content-Type,Content-Length或Transfer-Encoding,Host,Connection,Accept_Encoding,Cookie,User-Agent等
。 - 通过
CookieJar
读取 request 上的cookie - gzip压缩,同时对response响应数据进行gzip解压。
CacheInterceptor
CacheInterceptor
用于处理http缓存,若缓存中有所需请求的响应则后续Interceptor不再执行。
1、如果在初始化OkhttpClient的时候配置缓存,则从缓存中取caceResponse
2、将当前请求request和caceResponse 构建一个CacheStrategy对象
3、CacheStrategy
这个策略对象将根据相关规则来决定caceResponse和Request是否有效,如果无效则分别将caceResponse和request设置为null
4、经过 CacheStrategy
的处理(步骤3),如果request和caceResponse都置空,直接返回一个状态码为504,且body为Util.EMPTY_RESPONSE
的空Respone对象
5、经过CacheStrategy的处理(步骤3),resquest 为null而cacheResponse不为null,则直接返回cacheResponse对象
6、执行下一个拦截器发起网路请求,
7、如果服务器资源没有过期(状态码304)且存在缓存,则返回缓存
8、将网络返回的最新的资源(networkResponse)缓存到本地,然后返回networkResponse.
ConnectInterceptor
ConnectInterceptor
的主要职责是建立与服务器之间的连接,但这个事情它主要是委托给 StreamAllocation
来完成的。如我们前面看到的,StreamAllocation
对象是在 RetryAndFollowUpInterceptor
中分配的。ConnectInterceptor
通过 StreamAllocation
创建了 HttpStream
对象和RealConnection
对象,随后便调用了realChain.proceed()
,向连接中写入HTTP请求,并从服务器读回响应。
CallServerInterceptor
CallServerInterceptor
首先将http请求头部发给服务器,如果http请求有body的话,会再将body发送给服务器,继而通过httpStream.finishRequest()结束http请求的发送。随后便是从连接中读取服务器返回的http响应,并构造Response。如果请求的header或服务器响应的header中,Connection值为close,CallServerInterceptor
还会关闭连接。
最后便是返回Response。
代理路由与网络请求
在理顺了OkHttp的请求框架之后,剩下的就是最基础也是最重要的部分——okhttp是如何处理代理和路由的以及怎么建立TCP连接。这一块可能需要从计算机网络部分开始讲起,所以暂且放着等日后复习完计算机网络基础再来补充(其实就是觉得细节太多自己把捏不住)
可以先参考一下几篇文章: