- 分析版本,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
 26- Response 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)来维护缓存的更新。