OkHttp
针对一个新东西,我们首先要有几个疑问
- 是什么?
- 用来干什么的?
- 为什么要用它,有什么优点吗?
然后才是 - 怎么用?
带着问题思考,看完以下内容,回答问题。
前言:
OkHttp是最常用的网络请求框架,Retrofit2也是针对OkHttp的封装,底层还是用的OkHttp。今天就来看看OkHttp的源码,学习一下框架的原理和设计思想。
这篇文章主要针对OKHttp的工作原理进行分析,着重介绍OKHttp实现的原理以及工作流程。
原理以及工作流程分析
首先我们看框架的使用,一步步来分析
OkHttpClient client = new OkHttpClient();//创建OkHttpClient对象
Request request = new Request.Builder()
.url(url)//请求链接
.get()// 可省略,默认GET方法
.build();//创建Request对象
Response response = client.newCall(request).execute();//获取Response对象
以上代码是OkHttp的GET请求的同步请求用法。可以看到
step1. 创建OkHttpClient对象
step2. 创建Request请求
step3. 发起请求,获取请求结果Response。
我们根据请求流程开始分析OkHttp的工作原理。
step1.创建OkHttpClient对象
public OkHttpClient() {
this(new Builder());
}
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
//......
this.connectTimeout = builder.connectTimeout;
this.readTimeout = builder.readTimeout;
this.writeTimeout = builder.writeTimeout;
this.pingInterval = builder.pingInterval;
}
上面是OkHttpClient的构造函数,可以看到OkHttpClient有两个构造函数。根据构造方法的代码,很容易发现在构造方法中主要用builder(这里使用了建造者模式)设置了一些OKHttp的属性。比如:超时设置、拦截器、HTTPS相关等。
step2. 创建Request请求:
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tag = builder.tag != null ? builder.tag : this;
}
可以看到Request也是通过建造者模式创建的,在这里配置了url、method、请求头等信息。
step3.发起请求
在OkHttpClient和Request都创建好之后,就开始发起HTTP请求了。OkHttp中请求的方式分为同步请求(client.newCall(request).execute() )和异步请求(client.newCall(request).enqueue())两种,其中同步请求和异步请求的区别就是同步请求会阻塞当前线程,异步请求会放到线程池中执行。
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
可以看到通过newCall()方法创建了RealCall实例,然后通过RealCall.newRealCall()发起请求。接下来我们看异步请求。异步请求调用了RealCall的enqueue()方法。
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
在这里,OkHttp通过调度器Dispatcher执行请求。
/**Dispatcher**/
synchronized void enqueue(AsyncCall call) {
//这里判断队列是否已满,队列不满怎将请求放到线程池中执行,否则加入到队列中
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
可以看到enqueue()方法是一个同步方法(synchronized修饰),在这里首先判断了请求队列是否已满,如果不满,则开始在线程池中执行请求AsyncCall。AsyncCall继承了NamedRunable抽象类,而NamedRunnable继承了Runnable接口,在run()方法中调用了execute()方法。
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);
}
}
//......
finally {
client.dispatcher().finished(this);
}
}
在这里开始了OkHttp的核心请求部分。在OkHttp中使用了责任链模式处理这一部分的请求。
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)); //连接拦截器,创建HTTP连接
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);
}
OkHttp的拦截器Interceptor
在上面的代码中OkHttp通过各种拦截器处理请求。那拦截器究竟是什么东西呢?
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
@Nullable Connection connection();
Call call();
int connectTimeoutMillis();
Chain withConnectTimeout(int timeout, TimeUnit unit);
int readTimeoutMillis();
Chain withReadTimeout(int timeout, TimeUnit unit);
int writeTimeoutMillis();
Chain withWriteTimeout(int timeout, TimeUnit unit);
}
}
拦截器是一个接口,各种类型的拦截器实现了这个接口。以下是常见的拦截器:
. 重试拦截器(RetryAndFollowUpInterceptor):请求在失败的时候重新开始的拦截器。
. 桥接拦截器(BridgeInterceptor):主要用来构造请求。
. 缓存拦截器(CacheInterceptor):主要处理HTTP缓存。
. 连接拦截器(ConnectInterceptor):主要处理HTTP链接。
. 网络请求拦截器(CallServerInterceptor):负责发起网络请求。
拦截器是OkHttp发起请求的核心部分,接下来我们针对各种拦截器进行分析。上面的代码中,通过RealInterceptorChain的process()方法开始执行拦截器。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
calls++;
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next); //执行拦截器
//......
return response;
}
重试拦截器---RetryAndFollowUpInterceptor
这里我们以RetryAndFollowUpInterceptor为例,分析RetryAndFollowUpInterceptor的intercept()方法
public Response intercept(Chain chain) throws IOException {
//......
int followUpCount = 0;
Response priorResponse = null;
//通过一个循环来重新尝试请求
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
//1.调用下一个拦截器
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
//......
} catch (IOException e) {
//......
}
//......
//2.检测response是否合法
Request followUp = followUpRequest(response);
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
//3.返回response,请求完成
return response;
}
//最多尝试20次
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//4.重新设置请求
request = followUp;
priorResponse = response;
}
}
在RetryAndFollowUpInterceptor中我们可以看到请求的重试是由一个无线循环保持的,同时在代码里还限制了请求次数,最多20次。RetryAndFollowUpInterceptor的具体逻辑是:
- 开启循环,继续调用下一个拦截器直到返回结过;
- 通过followUpRequest()方法检查response是否合法,检查逻辑是根据HTTP返回码检测。如果合法followUp为null,则返回结果,否则进行下一步;
- 重新设置request、response(用于接下来重新构造response),执行第一步。
BridgeInterceptor
我们看看BridgeInterceptor做了哪些事。
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
从代码里可以看到,BridgeInterCeptor中设置了请求头的各种参数,比如:Content-type、Connection、User-Agent、GZIP等。
CacheInterceptor
缓存拦截器主要是处理HTTP请求缓存的,通过缓存拦截器可以有效的使用缓存减少网络请求。
public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null? cache.get(chain.request()): null;//1.取缓存
long now = System.currentTimeMillis();
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); //2.验证缓存
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse; //获取缓存
if (cache != null) {
cache.trackResponse(strategy);
}
// If we're forbidden from using the network and the cache is insufficient, fail.
//这里表示禁止使用缓存
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// If we don't need the network, we're done.
//3.直接返回缓存
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
//4.没有缓存,执行下一个拦截器
networkResponse = chain.proceed(networkRequest);
}
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (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();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
//5.更新缓存
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//......
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
//6.保存缓存
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
}
return response;
}
OkHttp会首先取出缓存,然后经过验证处理判断缓存是否可用。流程如下:
- 根据请求(以Request为键值)取出缓存
- 验证缓存是否可用,可用,则直接返回缓存,否则进行下一步;
- 继续执行下一个拦截器,知道返回结果;
- 如果之前有缓存,则更新缓存,否则新增缓存。
https://juejin.im/post/6844903743951994887
https://juejin.im/post/6844903903033557005#heading-0
https://www.jianshu.com/p/37e26f4ea57b