OkHttp的简介与使用
- 简介:OkHttp是square公司开源出来的一个优秀的网络请求框架。Android4.4开始HttpUrlConnection的底层实现采用的就是okhttp。
特点:
1
2
3
4支持HTTP/2 和 SPDY
默认支持 GZIP 降低传输内容的大小
支持网络请求的缓存
当网络出现问题时,自动重试一个主机的多个 IP 地址使用步骤
1 | * 使用步骤: |
分发器
OkHttp其实主要就是两部分:分发器和拦截器。分发器内部维护队列与线程池,完成请求调配。
我们根据使用流程来看一下,首先client.newCall(request)返回的是一个RealCall对象,也就是说client.newCall(post_request).enqueue()其实调用的就是RealCall的enqueue方法。
1 | //RealCall 类 |
执行请求最终是通过Dispatcher类来调用的,Dispatcher就是OkHttp的分发器。
1 | //Dispatcher 类 |
Dispatcher类维护了两个队列,readyAsyncCalls等待执行的队列,runningAsyncCalls正在执行的队列。
采用ArrayDeque,但是目前来说没有用到双向的操作,也许为了以后的扩展吧。
Dispatcher还维护了一个线程池,见上一篇Okhttp的线程池
当正在执行的队列大小<64并且相同host请求不能超过5个的时候,将请求放入runningAsyncCalls队列,并取线程执行。否则放入readyAsyncCalls队列等待。
那么为什么可以将AsyncCall交给线程池,到底执行的是什么呢?由源码可见AsyncCall最终还是继承的Runnable。
1 | //AsyncCall 类 |
一次请求结束后会调用Dispatcher的finished方法,在这里会将readyAsyncCalls中的call放入runningAsyncCalls去执行。
1 | //Dispatcher类 |
拦截器
OkHttp默认有五个拦截器,五大拦截器完成整个请求过程。Okhttp使用责任链模式将这五大拦截器串起来了。
1 | Response getResponseWithInterceptorChain() throws IOException { |
RetryAndFollowUpInterceptor 重试重定向拦截器
在交给桥接拦截器前,负责判断用户是否取消了请求;在获得了结果之后会根据响应码判断是否需要重定向,如果满足条件就会重启执行所有拦截器。
主要是对响应做的处理。
1 | //RetryAndFollowUpInterceptor 类 |
BridgeInterceptor 桥接拦截器
在交给缓存拦截器前,负责将HTTP协议必备的请求头加入其中并添加一些默认的行为;在获得了结果后,调用保存cookie接口并解析GZIP数据。
1 | //BridgeInterceptor 类 |
如果要使用Cookie的话,创建OkHttpClient的时候需要调用cookieJar处理。
1 | OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder() |
CacheInterceptor 缓存拦截器
在交给连接拦截器前,读取并判断是否使用缓存;获得结果后判断是否缓存。OkHttp只支持Get缓存。如果要使用OkHttp的缓存,则需要在创建OkHttpClient的时候设置Cache
1 | int cacheSize = 100 * 1024 * 1024; // 10MB |
我们看一下Cache的构造方法:
1 | public Cache(File directory, long maxSize) { |
可以知道,Cache用的是开源的缓存文件类DiskLruCache。OkHttp没有内存缓存,只有硬盘缓存。
1 | //CacheInterceptor 类 |
CacheStrategy是OkHttp的缓存策略类 ,主要维护了两个字段networkRequest和cacheResponse。
networkRequest | cacheResponse| 说明
—|—|—|
Null | Not Null | 直接使用缓存
Null | Null | okhttp直接返回504
Not Null | Null | 向服务器发起请求
Not Null | Not Null | 发起请求,若得到响应为304(无修改),则更新缓存并返回
总的来说就是,networkRequest若存在则优先发起网络请求,否则使用cacheResponse缓存,若都不存在则请求失败。
那么我们来看一下这两个字段值是怎么得出来的:
1 | public CacheStrategy get() { |
ConnectInterceptor 连接拦截器
在交给请求服务器拦截器前,负责找到或者新建一个连接,并获得对应的socket流;获得结果后不进行额外的处理。
1 | //ConnectInterceptor 类 |
Socket和服务器IP/PORT是对应的。ConnectionPool是Socket的连接池,默认最大闲置连接有5个,保活时间5分钟。
1 | public ConnectionPool() { |
1 | //如果清理任务未执行就启动它,再把连接加入到队列 |
cleanupRunnable的过程是什么?也就是说连接池是怎么移除连接的?
会对连接池遍历,获得在用连接数、闲置连接数、最大的闲置连接,如果最大的闲置连接超过了保活时间5分钟或者连接池内连接超过了5个,则马上移除它;否则如果有闲置连接但是还不到5分钟,则(5分钟-闲置时间)后再检查;否则有使用中的连接,等5分钟后再检查。
1 | @Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) { |
CallServerInterceptor 请求服务器拦截器
进行的真正的与服务器通信,将组装成HTTP报文格式发送到服务器,解析读取的响应数据。
如果请求头有 Expect:100-continue ,代表了先询问服务器是否愿意接收发送的请求体数据。一般出现于上传大容量请求体或者需要验证的情况下。OkHttp的做法是,如果服务器允许则返回100,客户端继续发送请求体,如果服务器不允许则直接返回给用户,同时服务器也可能会忽略此请求头,一直无法读取应答,此时抛出超时异常。
OkHttp的总结
OkHttp用了哪些设计模式
- 责任链模式
五大拦截器链式调用,这也是okhttp的精髓所在。可以降低逻辑的耦合,相互独立的逻辑写到自己的拦截器中;扩展性强,可以添加新的拦截器。
2. 构建者模式
在Okhttp中,建造者模式也是用的挺多的,主要用处是将对象的创建与表示相分离,用Builder组装各项配置。
3. 单例模式
Platform类中用了单例模式
- 享元模式
线程池的使用。
5. 工厂模式
CacheStrategy这个对象的生成用的就是工厂模式。
response.body().string() 只能调用一次
之前在使用网上某个开源库的时候也遇到过,这里先不多谈。当我们解析response的时候,有时候会直接打印数据,如果你写了两次response.body().string()会有异常的。
response.body()返回的是ResponseBody对象。
1 | //ResponseBody 类 |
可知,Util.closeQuietly(source)关闭了 ResponseBody 子类所持有的 BufferedSource 接口对象。当我们第一次调用 response.body().string() 时,OkHttp 将响应体的缓冲资源返回的同时,调用 closeQuietly() 方法默默释放了资源。
为什么要这么设计呢?下面这个回答也是在网上找到的答案哈哈。
在实际开发中,响应主体 RessponseBody 持有的资源可能会很大,所以 OkHttp 并不会将其直接保存到内存中,只是持有数据流连接。只有当我们需要时,才会从服务器获取数据并返回。同时,考虑到应用重复读取数据的可能性很小,所以将其设计为一次性流(one-shot),读取后即 ‘关闭并释放资源’。