Okhttp的优点
- 支持Http2所有请求共享同一个socket
- 通过间接池(http1.x socket复用)减少请求延迟
- 通过间接池(http1.x socket复用)减少请求延迟
- 使用缓存
- 自动重定向
Okhttp分发器
//请求流程-分发器
Disoarcher.enqueue(AsyncCall){
//执行队列小于64,同一个Host请求小于5
if (runningAsyncCalls.size < 64) {
//ArrayDeque双向队列
runningAsyncCalls.add
executorService().excute(call){
response = call.excute()
dispatcher.finished(){
runingAsycnCalls.remove(call)
runningAsyncCalls.add(readyAsyncCalls.next())
readyAsyncAclls.remove()
}
}
} else{
readyAsyncCalls.add
}
}
//分发器--线程池
executorService(){
//线程池策略:当新建一个任务时,首先会判断当前任务数量是否小于核心线程数量,小于的话直接执行。否则会判断当前任务队列是否还有位置,有的话会添加到任务队列中等待执行。当任务队列也满了的话会判断最大线程数量,如果当前线程数量小于最大线程数量,则创建新的线程去执行。
//使用Synchonous队列是因为这个队列没有容量,不会出现队列任务被核心线程阻塞导致无法执行的状况,保证了并发线程的最大吞吐量。
new threadPoolExecutor(0,MAX_VALUE,60,TimeUnit.SECONDS,SynchonousQueue())
}
拦截器
-
RetryAndFollowUpInterceptor(重试和重定向拦截器):
- 主要负责处理请求失败时的重试逻辑,以及在需要时进行重定向。例如,当请求因为网络问题失败时,它会尝试重新发送请求;当响应状态码指示需要重定向时,它会构建新的请求并发送。
-
BridgeInterceptor(桥接拦截器):
- 主要负责将用户请求补全(如添加必要的头部信息),并对响应进行额外处理(如添加缓存头部信息)。它作为用户请求与服务器响应之间的桥梁,确保请求和响应符合 HTTP 协议要求。
-
CacheInterceptor(缓存拦截器):
- 在请求发送前,它首先会查询缓存,如果缓存有效则直接返回缓存的响应,避免网络请求。同时,它也会判断服务器响应是否需要被缓存,如果需要,则将响应存储到缓存中。
-
ConnectInterceptor(连接拦截器):
- 负责与服务器建立 TCP 连接。它处理与服务器之间的连接复用,以及连接失败时的重试逻辑。
-
CallServerInterceptor(请求服务器拦截器):
- 这是实际与服务器通信的拦截器,负责将请求数据发送给服务器,并解析服务器返回的响应数据。它会封装请求数据为 HTTP 报文,并将服务器返回的响应数据解析为 OkHttp 的
Response对象。
- 这是实际与服务器通信的拦截器,负责将请求数据发送给服务器,并解析服务器返回的响应数据。它会封装请求数据为 HTTP 报文,并将服务器返回的响应数据解析为 OkHttp 的
责任链模式
OkHttp中的责任链模式是一种设计模式,用于处理网络请求的拦截和响应。在OkHttp中,责任链模式通过拦截器(Interceptor)实现,每个拦截器可以处理请求或响应,或者将请求或响应传递给链中的下一个拦截器。这种模式允许开发者自定义网络请求的处理逻辑,如添加日志、修改请求头、缓存响应等。
getResponseWithInterceptorChain(){
//创建拦截器列表
List<Interceptor> interceptors = new ArrayLists();
interceptors.add();
interceptors.add();
interceptors.add();
//组件责任链条,传入拦截器列表
Chain chain = new Chain(interceptors);
//责任链开始执行
chain.proceed{
//获取第一条拦截器
interceptor = interceptors.get(0);
//执行拦截器的方法,并接受一个新的Chain对象,其中Chain的责任链去除掉当前的拦截器
interceptor.intercept(new Chain(interceptors(index-1))){
//。。。执行拦截器内部实际逻辑
//继续执责任链的下一条数据并向上返回
return chain.proceed();
}
}
}
在 OkHttp 客户端配置中,我们通过 addInterceptor 方法将拦截器添加到责任链中。当发起请求时,这些拦截器会按照添加的顺序依次执行,每个拦截器都可以对请求或响应进行处理,并最终调用 chain.proceed(request) 方法将请求传递给下一个拦截器或最终的请求执行者。
这种设计模式使得我们可以灵活地扩展和修改请求处理流程,而不需要修改现有的代码逻辑。
RetryAndFollowUpInterceptor
主要处理响应是否需要重试和重定向
// 需要重定向的HTTP状态码
private static final int[] REDIRECT_CODES = {301, 302, 303, 307, 308};
// 最大重试次数
private static final int MAX_RETRY_COUNT = 3;
Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
int retryCount = 0;
while (retryCount < MAX_RETRY_COUNT) {
try {
response = chain.proceed(request);
// 检查响应是否需要重试
if (!response.isSuccessful() && shouldRetry(response)) {
retryCount++;
// 可以在这里修改请求,比如增加一些头部信息
// request = modifyRequestForRetry(request);
continue; // 重试请求
}
// 检查响应是否需要重定向
if (isRedirectNeeded(response)) {
String redirectUrl = response.getHeader("Location");
if (redirectUrl != null) {
// 创建新的请求,使用重定向的URL
request = createRedirectRequest(request, redirectUrl);
continue; // 执行重定向请求
}
}
// 如果不需要重试也不需要重定向,则返回响应
return response;
} catch (IOException e) {
// 处理IO异常,根据需要决定是否重试
if (shouldRetryOnException(e)) {
retryCount++;
continue; // 重试请求
} else {
throw e; // 抛出异常,终止请求
}
}
}
// 如果达到最大重试次数仍然失败,则抛出一个异常或返回特定的错误响应
throw new IOException("Failed to get successful response after " + MAX_RETRY_COUNT + " retries.");
}
// 判断响应是否需要重试
private boolean shouldRetry(Response response) {
// 根据响应码或其他条件判断是否需要重试
// 比如,可以重试500服务器错误或502坏网关错误等
return response.code() == 500 || response.code() == 502 || response.code() == 503;
}
// 判断响应是否需要重定向
private boolean isRedirectNeeded(Response response) {
// 检查响应码是否在需要重定向的HTTP状态码列表中
for (int code : REDIRECT_CODES) {
if (response.code() == code) {
return true;
}
}
return false;
}
// 创建重定向请求
private Request createRedirectRequest(Request originalRequest, String redirectUrl) {
// 根据原始请求和重定向URL创建新的请求
// ... 创建逻辑 ...
return new Request.Builder()
// ... 设置请求的属性,比如URL、头部信息等 ...
.url(redirectUrl)
.build();
}
// 判断异常是否需要重试
private boolean shouldRetryOnException(IOException e) {
// 根据异常类型或其他条件判断是否需要重试
// 比如,可以重试SocketTimeoutException或UnknownHostException等
return e instanceof SocketTimeoutException || e instanceof UnknownHostException;
}
BridgeInterceptor
添加了以下请求头
-
Content-Type:如果请求体(RequestBody)存在且内容类型(MediaType)不为空,则会添加
Content-Type请求头,用于指示资源的MIME类型。 -
Content-Length:如果请求体的内容长度是固定的,则会添加
Content-Length请求头,用于指示请求体的长度(以字节为单位)。 -
Transfer-Encoding:如果请求体的内容长度是未知的(即内容长度不是固定的),则会添加
Transfer-Encoding: chunked请求头,表示请求体将被分成多个块进行传输。 -
Host:如果请求中没有设置
Host请求头,则会添加Host请求头,其值为请求的URL的主机部分。 -
Connection:如果请求中没有设置
Connection请求头,则会添加Connection: Keep-Alive请求头,用于指示希望保持TCP连接活跃。 -
Accept-Encoding:如果请求中没有设置
Accept-Encoding请求头,并且也没有设置Range请求头,则会添加Accept-Encoding: gzip请求头,用于指示客户端支持gzip压缩。 -
Cookie:如果请求URL对应的Cookie不为空,则会添加
Cookie请求头,用于将Cookie信息发送给服务器。 -
User-Agent:如果请求中没有设置
User-Agent请求头,则会添加User-Agent请求头,其值通常为OkHttp的版本信息或其他自定义的用户代理字符串。
CacheInterceptor
@Override
public Response intercept(Chain chain) throws IOException {
// 尝试从缓存中获取响应
Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;
long now = System.currentTimeMillis();
// 创建缓存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
// 从缓存策略中获取网络请求和缓存响应
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
// 如果缓存策略指示不需要网络请求,并且存在有效的缓存响应,则直接返回缓存响应
if (cache != null) {
cache.trackResponse(strategy);
}
// 如果缓存策略指示不需要网络请求,但缓存响应为空,则返回一个错误响应
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body());
// ...
}
// 如果缓存策略指示不需要网络请求,并且存在有效的缓存响应,则直接返回缓存响应
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
// 如果需要发起网络请求,则调用下一个拦截器进行处理
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// ...
}
// 如果服务器返回304状态码,表示内容未修改,使用缓存响应
if (cacheResponse != null && networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
}
// 否则,返回网络响应,并根据需要更新缓存
// ...
}
networkRequest |
cacheResponse |
是否启用缓存 | 说明 |
|---|---|---|---|
null |
非null
|
是 | 缓存策略决定可以直接使用缓存响应,无需发送网络请求。 |
非null
|
null |
否 | 缓存中不存在有效响应,需要发送网络请求获取响应。 |
非null
|
非null
|
视情况而定 | 缓存响应存在但需要验证其有效性,或需要更新缓存。 |
null |
null |
否 | 这种情况通常不会发生,因为至少会有一个非空的请求。 |
ConnectInterceptor
ConnectionPool:Socket连接池,默认持有5个socket连接,连接时间最长为5min。
public class CustomConnectInterceptor implements Interceptor {
private final OkHttpClient client;
private final ConnectionPool connectionPool;
public CustomConnectInterceptor(OkHttpClient client) {
this.client = client;
this.connectionPool = client.connectionPool(); // 获取 OkHttpClient 的连接池
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 尝试从连接池中获取连接
RealConnection connection = null;
try {
connection = connectionPool.get(request.url(), null, null, false);
} catch (IOException e) {
// 连接获取失败,可能是连接池为空或没有合适的连接
// 可以在这里进行日志记录或异常处理
}
// 如果连接为空,则需要创建一个新的连接(这部分逻辑通常由 OkHttp 内部处理)
if (connection == null) {
// ...(创建新连接的逻辑,这里省略)
}
// 假设我们已经有了连接,现在创建 HttpCodec
HttpCodec httpCodec = new Http1Codec(client, connection); // 示例中使用 Http1Codec,实际应根据连接类型选择
// 调用下一个拦截器,并传入请求、连接和 HttpCodec
return chain.proceed(request, new StreamAllocation(/* ... */, connection, /* ... */), httpCodec, connection);
}
// ...(其他可能的方法和逻辑)
}
CallServerInterceptor
职责:Request->报文、报文->Response
-
获取请求和连接信息:
CallServerInterceptor从拦截器链中接收请求(Request)和连接信息(如RealConnection和HttpCodec)。这些信息在之前的拦截器中已经被处理和准备。 -
写入请求头:
使用HttpCodec的writeRequestHeaders方法将请求头写入到输出流中。这一步是向服务器发送请求的第一部分,它包含了请求的 HTTP 方法、URL、协议版本以及任何自定义的请求头。 -
处理请求体:
如果请求包含请求体(例如 POST 请求),CallServerInterceptor将检查请求头中是否包含"Expect: 100-continue"。如果包含,它将等待服务器发送一个 100 Continue 响应,然后再发送请求体。这样做是为了在发送大量数据之前,先确认服务器愿意接收请求体。 -
读取响应头:
发送完请求头和请求体后,CallServerInterceptor将使用HttpCodec的readResponseHeaders方法读取服务器的响应头。这些响应头包含了服务器的状态码、响应类型以及其他重要的响应信息。 -
处理响应体:
根据响应头的指示,CallServerInterceptor可能会读取响应体。响应体的处理可能包括将响应数据转换为字节流、字符流或直接写入文件等。 -
返回响应:
最后,CallServerInterceptor构建一个Response对象,包含从服务器读取的所有信息(如状态码、响应头、响应体等),并将其返回给拦截器链中的下一个拦截器(如果有的话)。如果没有下一个拦截器,则这个Response对象将被返回给 OkHttp 的调用者。