1. 创建 OkHttpClient 对象
可以直接新建,也可以用建造者模式建造出来。直接新建时,其实也是使用建造者设置了默认的请求参数。
OkHttpClient client = new OkHttpClient();
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(1000, TimeUnit.SECONDS)
.readTimeout(1000, TimeUnit.SECONDS)
.build();
功能 | 函数 | 注释 |
---|---|---|
添加应用拦截器 | addInterceptor(Interceptor) | 最接近应用的拦截器 |
添加网络拦截器 | addNetworkInterceptor(Interceptor) | 接近网络的拦截器 |
设置缓存对象 | cache(Cache cache) | 使用 DiskLruCache 实现 |
okhttp3.Cache
构造时只需要指定缓存目录和大小即可。
网络拦截器实例:给返回的响应添加缓存过期时间
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Response originResponse = chain.proceed(chain.request());
// 5 分钟后过期
CacheControl.Builder builder = new CacheControl.Builder()
.maxAge(5, TimeUnit.MINUTES);
return originResponse.newBuilder()
.header("Cache-Control", builder.build().toString())
.build();
}
};
2. 准备好 Request 对象
通常用建造者模式来建造。
new Request.Builder().build();
功能 | 函数 | 注释 |
---|---|---|
添加 URL | url(String url) | |
GET 请求 | get() | |
POST 方式提交表单 | post(RequestBody body) | |
替换请求头 | header(String name, String value) | 先删除 name 对应的原有的字段对,再添加 |
添加请求头 | addHeader(String name, String value) | 直接往 ArrayList 中添加两个 String |
表单的构建
new FormBody.Builder().build()
功能 | 函数 | 注释 |
---|---|---|
添加表单项 | add(String name, String value) | 没有 encode 的字符串 |
添加表单项 | addEncoded(String name, String value) | 已经 encode 的字符串 |
3. 发送请求
OkHttpClient 实例先用 newCall(Request) 方法对请求再一次包装,返回 Call 对象,表示请求已经准备好。然后有同步和异步两种方式来发送请求:
同步方式:
Response response = client.newCall(request).execute();
异步方式:
Response response = client.newCall(request).enqueue(callback);
异步方式会用一个 CachedThreadPool 线程池来管理异步任务,任务一提交就马上在非核心线程中执行,非核心线程的空闲存活期是 60s。
- enqueue 方法会传入一个 okhttp3.Callback 对象,需要注意的是回调时还是在子线程。
new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 通过runOnUiThread()方法回到主线程处理逻辑
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
Toast.makeText(getContext(), "加载失败", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
}
- enqueue 方法中,callback 被包装成实现了 Runnable 接口的 AsyncCall 对象,传入 OkHttpClient 的 Dispatcher 。Dispatcher 会使用线程池执行 AsyncCall 的 execute 方法,最终的执行结果回调 Callback。
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
}
4. 拦截器
不论是 execute 方法还是 enqueue 方法发送链接请求,最终都会调用 getResponseWithInterceptorChain
方法。
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.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);
}
在请求往外发送时一次经过7个拦截器,第2~5个可以认为是 OkHttpCore 核心。
序号 | 名称 | 注释 |
---|---|---|
1 | 应用拦截器 interceptors |
由用户在创建 OkHttpClient 时设置 |
2 | 重试和重定向拦截器 RetryAndFollowUpInterceptor |
1.网络请求出现异常时,如果满足重试条件就发送重试请求; 2.如果网络响应包含重定向信息,就创建重定向请求并发送 |
3 | 桥接拦截器 BridgeInterceptor |
1.深加工用户传递来的请求,设置默认请求头; 2. 用户没有设置时默认采用 gzip 压缩解压数据 |
4 | 缓存拦截器 CacheInterceptor |
缓存的查找和保存 |
5 | 连接拦截器 ConnectInterceptor |
给网络请求提供一个连接,之后拦截器中 chain.connection() 才不为 null |
6 | 网络拦截器 networkInterceptors |
由用户在创建 OkHttpClient 时设置 |
7 | 呼叫服务器拦截器 CallServerInterceptor |
网络请求最终从这里发送出去,并包装响应对象 |
5. CacheInterceptor 详解
网络请求中与缓存有关的头域:
序号 | 名称 | 注释 |
---|---|---|
1 | Cache-Control:max-age | 缓存的推荐保质期 |
2 | Cache-Control:max-stale | 缓存超过保质期多久仍可接受 |
3 | Cache-Control:min-fresh | 缓存距离推荐保质期还有多久就认为缓存不新鲜了 |
4 | Cache-Control:immutable | 缓存永不过期 |
5 | Cache-Control:only-if-cached | 只允许使用当前缓存数据,没有缓存就只能返回 504 响应 |
6 | Cache-Control:No-Cache | 不希望使用缓存 |
7 | Age | 响应对象在代理缓存中存在的时间,以秒为单位 |
8 | Date | 当前的GMT时间 |
9 | ETag | 对于某个资源的某个特定版本的一个标识符 |
10 | Expires | 超过该时间则认为此回应已经过期 |
11 | Last-Modified | 服务器资源的最后修改时间 |
相关响应头:
序号 | 名称 | 注释 |
---|---|---|
1 | 200 OK | 合适的资源作为响应体传回客户端 |
2 | 301 Moved Permanently | 所请求的 URL 资源路径已经改变, 新的 URL 在响应的 Location 头字 段 |
3 | 304 Not Modified | 所请求的内容距离上次访问并没有 变化,浏览器缓存有效 |
4 | 404 Not Found | 服务器找不到所请求的资源 |
5 | 504 Gateway TImeout | 服务器作为网关不能从上游服务器 得到响应返回给客户端 |
下面将介绍缓存有效性相关的 3 个概念:
- 新鲜度检测
- 在验证
- 在验证命中
在使用缓存将 url 响应资源的副本存储在本地时,当我们再次对该 url 资源发起请求时,可以快速从本地存储中获取该 url 资源,而不需要重新发起网络连接。
但是,服务器的 url 资源可能在一定时间后被修改,因此我们不能一直使用缓存资源,在一定时间之前,认为可以使用缓存资源,在这个时间之后认为不能再使用缓存资源,需要重新请求网络资源。这个条件的判断就是 新鲜度检测。就像我们在吃食品之前要看看它有没有过期。
url 资源缓存超过一定时间,我们也不会直接重新请求 url 资源,而是去服务器查看资源是否已经发生改变,这就叫 再验证。
如果服务器发现 url 资源没有改变,就返回 304 Not Modified,并不在返回对应实体,这就叫 再验证命中,此时我们可以继续使用 url 资源缓存。如果发生了变化,则返回 200 OK,并将改变后的 url 资源返回。
具体实现:
新鲜度检测
http1.1 规范中,通过响应首部的 Cache-Control:max-age 和 Last-Modified 计算出绝对时间。http1.0 规范中,响应首部 Expires 的值就是绝对时间。
再验证
超过绝对时间后要进行再验证,需要根据响应首部具体的规范来进行 条件请求,通常有 5 种条件请求首部。
序号 | 名称 | 注释 |
---|---|---|
1 | If-Modified-Since | 和响应首部 Last-Modified 配合使用,询问 服务器 url 资源的最后修改时间是否变化, 没有的话就返回 304 未修改 |
2 | If-None-Match | 和响应首部 Etag 配合使用,Etag 相当于服务器 对 url 资源定义的粗粒度的版本号,即使资源修 改了,但如果版本号没有变化,仍然认为再验证 命中。 |
3 | If-Unmodified-Since | |
4 | If-Range | |
5 | If-Match |
在 CacheInterceptor 源码中,首先获取缓存的 Response 对象(可能为 null)。然后用缓存的 Response,以及当前的时间和 Request 构建 CacheStrategy 对象。
CacheStrategy strategy = new CacheStrategy
.Factory(now, chain.request(), cacheCandidate).get();
CacheStrategy 有 networkRequest
和 cacheResponse
两个变量,根据工厂方法传入的三个参数,get 方法中有 5 种判断条件来设置它们的值。
时序 | 条件 | networkRequest |
cacheResponse |
---|---|---|---|
1 | 没有缓存时 2. 请求采用 https 但是缓存没有进行握手的数据 3. 缓存不应该被保存(保留了一些不应该缓存的数据) 4. 请求添加了 Cache-Control:No-Cache ,或者一些条件请求首部,说明不希望使用缓存 |
传入的 resquest | null |
2 | 缓存响应首部包含 Cache-Control:immutable (不属于 http 协议),说明资源不会改变 |
null | 传入的缓存响应 |
3 | 新鲜度验证通过 | null | 传入的缓存响应 (可能会添加一些首部) |
4 | 新鲜度验证不通过,使用 Etag 或 Last-Modified 或 Date 首部构造条件请求并返回 |
条件请求 | 传入的缓存响应 |
5 | 新鲜度验证不通过,且缓存响应没有 Etag、 Last-Modified 和 Date 中的任何一个 |
传入的 resquest | null |
6 | 上述 5 种情况中 networkRequest 不为空时,若 请求通过 Cache-Control:only-if-cached 只允许我们使用当前缓存 |
null | null |
在 CacheInterceptor 的 intercept 方法中,会根据创建的 CacheStrategy 对象的两个变量的值来进行处理。
时序 | networkRequest |
cacheResponse |
处理方法 |
---|---|---|---|
1 | null | null | 直接返回 504 Unsatisfiable Request (only-if-cached) 响应。 |
2 | null | 非 null | 说明缓存有效,直接返回 cacheResponse |
3 | 非 null | null 或 非 null |
说明需要向网络发送请求(原始 request 或新鲜度验证后 的条件请求): 1. 如果有缓存数据,在获得再验证的响应后,使用 cache 的 update 方法更新缓存; 2. 如果没有缓存数据,判断请求是否可以被缓存,可以的 话就使用 cache 的 put 方法缓存下来 |