- 分析版本,OkHttp-3.12.10
- 分析环境,IntellJIdeal
通过本文,可以了解如下内容:
- OkHttp的基本使用;
- OkHttp三次握手实现;
- OkHttp的拦截器实现;
- OkHttp的缓存实现;
- OkHttp如何实现WebSocket;
- 了解ArrayDeque,因为OkHttp中大量的使用ArrayDeque
基本使用
get请求
同步get请求
1 | public void getSync(String url) { |
异步get请求
1 | public void get(String url) { |
post请求
介绍post请求之前先了解下MediaType
。
MediaType指的是媒体类型,表示表示的时浏览器将以什么形式、什么编码对资源进行解析。主要包含三个信息:type、subType以charset,MediaType常见类型如下:
- text/(html,HTML文本格式;plain,纯文本格式;xml,xml格式;x-markdown,Markdown格式;)
- image/(gif,gif图片;jpeg,jpg图片;png,png图片;)
- application/(json,json格式数据;x-www-form-urlencode,键值对形式编码的参数;xhtml+xml,xhtml文本格式;xml,xml格式;pdf,pdf格式;msword,word格式;octet-stream,二进制数据(常用用于下载))
- multipart/(form-data,不对字符编码发送大量二进制数据或包含non-ASCII字符的文本)
上面每种格式的前半部分表示type(“/”前的部分),后半部分表示subtype(“/”后的部分);
发送表单
1 | public void postForm(String url) throws IOException { |
发送json
1 | public void postJson(String url) throws IOException { |
发送Multipart
1 | public void postMultiPart(String url) throws IOException { |
建立WebSocket连接
OkHttpClient 可以直接发送WebSocket连接请求,代码如下:
1 | /** |
具体的OkHttpClent WebSocket 说明,参考文章:OkHttp3实现WebSocket连接
基本使用小结
上面就是使用OkHttp进行网络请求的常用场景,总结下来就是:
- 获取
OkHttpClient
实例(一般是单例模式); - 构建
Request
,post请求需要先构建RequestBody
; - 调用OkHttpClient的
newCall(Request request)
方法获取Call
; - 获取Call对象后,
- 同步请求调用
Call.execute()
获取Response对象,并处理请求结果; - 异步请求调用
Call.enqueue(Callback callback)
,并在回调中处理结果;
- 同步请求调用
拦截器
拦截器是OkHttpClient设计的精髓所在,它采用采用责任链模式。责任链代码具体体现在如下地方:
1 | // RealCall.java |
1 | // RealInterceptorChain.java |
责任的传递是通过调用获取到的拦截器的intercept(Chain chain)
方法,并在方法内部调用Chain.proceed(Request request)
方法来实现的,最后一个拦截器CallServerInterceptor
的intercept(Chain chain)
方法中没有调用proceed(Request request)
方法,说明任务到这个拦截器的处理工作完毕了。
拦截器的种类
要了解拦截器的种类,还是得看RealCall
的getResponseWithInterceptorChain()
方法:
1 | public Response getResponseWithInterceptorChain() throw IOException { |
总结下责任链中各个节点拦截器的作用,如下:
拦截器 | 作用 |
---|---|
应用拦截器 | 拿到的是原始请求,可以添加一些自定义header、通用参数、参数加密、网关接入等等。 |
RetryAndFollowUpInterceptor | 处理错误重试和重定向 |
BridgeInterceptor | 应用层和网络层的桥接拦截器,主要工作是为请求添加cookie、添加固定的header,比如Host、Content-Length、Content-Type、User-Agent等等,然后保存响应结果的cookie,如果响应使用gzip压缩过,则还需要进行解压。 |
CacheInterceptor | 缓存拦截器,如果命中缓存则不会发起网络请求。 |
ConnectInterceptor | 连接拦截器,内部会维护一个连接池,负责连接复用、创建连接(三次握手等等)、释放连接以及创建连接上的socket流。 |
networkInterceptors(网络拦截器) | 用户自定义拦截器,通常用于监控网络层的数据传输。 |
CallServerInterceptor | 请求拦截器,在前置准备工作完成后,真正发起了网络请求。 |
RetryAndFollowUpInterceptor
处理错误和重定向。核心方法是followUpRequest(Response userResponse, Route route)
,该方法会根据返回的responseCode来进行相应的逻辑处理,最终得到相应的Request对象。这里主要看那些导致重试和重定向的responseCode:
- 重试,
HTTP_CLIENT_TIMEOUT
和HTTP_UNAVAILABLE
(重试一次); - 重定向,
HTTP_MULT_CHOICE
、HTTP_MOVED_PERM
、HTTP_MOVED_TEMP
、HTTP_SEE_OTHER
,会进行重定向的尝试;
还有一个比较核心的点就是,该拦截器的intercept方法中,完成了StreamAllocation
对象的创建,并将其值传递到了RealInterceptorChain中。
BridgeInterceptor
说明见上面的表格,需要注意的是,如果我们没有指定Accept-Encoding: gzip
,OkHttpClient在Response的header中存在Accept-Encoding: gzip
的情况下会默认帮我们处理Gzip压缩。但是如果我们制定了Accept-Encoding: gzip
,则需要我们自己进行Response的Gzip压缩处理了。
CacheInterceptor
为什么说命中缓存就不会发起网络请求呢?看看CacheInterceptor的Intercept(Chain chain)方法
1 | // 命中了缓存,直接return了,此时没有执行责任链,即这次网络请求的任务已经完成了。所以此时不会再执行网络请求了,因为负责建立连接ConnectInterceptor和真正执行网络请求CallServerInterceptor的拦截器都没有执行。 |
OkHttpClient 的缓存设计算法采用的是Lru,基于Okio实现(相当于基于依据Http的缓存设计思路,用Okio进行了实现)。关于OkHttpClient的缓存,打算单独分析。
ConnectInterceptor
拦截器作用于职责:内部会维护一个连接池,负责连接复用、创建连接(三次握手等等)、释放连接以及创建连接上的socket流。整个ConnectInterceptor的代码还是比较简洁的,但是主要的处理都封装到了StreamAllocation
这个类中了。StreamAllocation
建立连接的流程
StreamAllocation
的newStream(...)
;StreamAllocation
的findHealthyConnection(...)
,找到RealConnection
;findHealthyConnection(...)
的while(true)循环中调用findConnection(...)
,直到找到了一个健康的、可用的RealConnection
才退出循环;StreamAllocation
本身可以标记找到的RealConnection,方便下次查找;- 先通过当前请求地址,从ConnectionPool中找;
- 通过全局路由路径,从ConnectionPool中找;
- 都找不到,就要新建一个连接,完成TCP+TLS了()
调用RealConnection的connect(…)方法,里面会根据情况调用
connectTunnel
和connectSocket
,这个过程是阻塞的(一个while(true)循环,但最终都是调用connectSocket
,方法内部会调用不同平台的connectSocket
方法,创建成功后,将创建出来的RealConnection放入ConnectionPool。而三次握手也就发生在这个建立socket连接的过程中了,因此OkHttp的三次握手实现时依赖于socket的。了解三次握手
ConnectionPool内部有一个线程池,用来定期清理尝时间不用的RealConnection(毕竟RealConnection是系统资源)
CallServerInterceptor
真正发起网络请求的地方
问题
addInterceptor(Interceptor interceptor)
和addNetworkInterceptor(Interceptor interceptor)
区别?- 目的:前者是添加应用拦截器(此时请求还未真正发起,是一些与处理过程),后者是添加网络拦截器,它位于
ConnectInterceptor
和CallServerIntercptor
之间(此时网络链路已经准备好,只带请求发送了) - 执行时机:前者最先执行,后者在链接建立之后,请求发送之前;
- 调用:应用拦截器和网络拦截器的proceed(Chain chain)
- 目的:前者是添加应用拦截器(此时请求还未真正发起,是一些与处理过程),后者是添加网络拦截器,它位于
OkHttpClient 的Dispatcher是如何管理三个请求的队列的?
首先这三个队列分别是:正在运行的同步双端队列(runningSyncCalls)、准备就绪的异步双端队列(readyAsyncCalls)、正在运行的异步双端队列(runningAsyncCalls)
client.dispatcher().executed(this)
干了什么呢?
1 | synchronized void executed(RealCall call) { |
将RealCall对象加入到正在运行的同步队列中,这样在getResponseWithInterceptorChain()
调用结束得到Response对象后,最终运行finally块时,调用client.dispatcher().finished(this);
,那么看看这个方法:
1 | void finished(RealCall call) { |
getResponseWithInterceptorChain()
如何实现递归调用的?
先看getResponseWithInterceptorChain()
代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26Response 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的列表中最后都会添加一个 CallServerInterceptor ,这个 CallServerInterceptor 的Intercept方法中是不调用 Chain.proceed(Request request)方法的。
// 递归就是通过不停的调用Chain.process(Chain chain)方法的。相当于CallServerinterceptor是递归的终止条件。
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
Response response = chain.proceed(originalRequest);
if (retryAndFollowUpInterceptor.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
}所以递归是不断的调用
RealInterceptorChain
的proceed(Request request)
方法的,而递归终止的条件就是最终会走到CallServerInterceptor
的intercept(Chain chain)方法,而该方法不会再调用Chain.proceed(Request request)
总结
至此OkHttpClient的基本内容就介绍完毕了,OkHttpClient十分强大强大,
- 从设计模式角度,如对Http1和Http2的支持(采用的是策略模式),拦截器的处理(采用的是责任链模式),各种对象的创建如OkHttpClient、Request、Response(采用的Builder模式)
- 从同步处理的角度,ConnectionPool中的同步处理、三个请求队列的操作处理;
- 从线程池的角度,有Dispatcher(负责请求的分发调度)、ConnectionPool中的线程池(负责定期按规则清理不需要连接对象)
- 从缓存设计的角度,采用Okio将http的缓存策略实现,并通过LRU算法(DiskLruCache)来维护缓存的更新。