用过OkHttp的小伙伴们,都知道先new一个OkHttpClient的builder,紧接着添加
Interceptor
、NetWorkInterceptor
和连接超时等配置,然后通过build方法构建出OkHttpClient对象;第二部分接着new了一个Request的builder对象,设置了url和header等相关参数,接着也是通过build方法生成了Request对象;
最后通过第一步OkHttpClient的newCall传入request,拿到了Call对象,接着通过Call的同步方法execute和异步方法enqueue拿到Response,这里文字计较多,我直接上一个大家熟悉的代码:
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(10, TimeUnit.SECONDS);
builder.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Log.d(TAG, "Interceptor url:" + chain.request().url().toString());
return chain.proceed(chain.request());
}
});
builder.addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Log.d(TAG, "NetworkInterceptor url:" + chain.request().url().toString());
return chain.proceed(chain.request());
}
});
OkHttpClient build = builder.build();
Request request = new Request.Builder()
.url("https://www.baidu.com")
.build();
Call call = build.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "response:" + response.body().string());
}
});
其实分析源码我觉得突破点就是根据平时写的demo代码,然后一步步跟着进去看,虽然看的过程中很枯燥,但是当你坚持下来的时候,你就发现其实你也快能写出这样优秀的代码,然后反思,为什么会用这种写法,加自己的总结,相信你也会写出这么牛的代码。好了,下面开始源码分析部分。
源码分析
OkHttpClient.Builder
这块主要是通过builder模式配置Interceptor、NetWorkInterceptor、连接超时、读取超时等相关参数配置,这里列几个常见的属性:
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
final Dispatcher dispatcher;//同步和异步处理类
......
final List interceptors;//普通的拦截器
final List networkInterceptors;//netWork拦截器
......
final int connectTimeout;//连接超时
final int readTimeout;/读取超时
final int writeTimeout;//写入超时
......
}
单看这个类是没什么好说的,主要有dispatcher
对象,做同步和异步任务入口类,这个后面会介绍,第二个就是两种类型的拦截器,后面会分析这两种拦截器的区别,以及平时该怎么使用,第三个就是超时的参数配置。其他的属性在时去掉了,方便我们分析流程。
Request.Builder
这块主要是配置请求的url、请求方式(GET、PUT等配置)、header和body等相关配置。里面的配置比较少,直接看属性:
public final class Request {
final HttpUrl url;//请求路径
final String method;//请求方式
final Headers headers;//请求头的key value配置
final @Nullable RequestBody body;//请求体的key value配置
final Map, Object> tags;//请求tag标识
}
OkHttpClient.newCall
这块也很简单,通过传入进来的request对象,返回了Call对象:
Call
是一个接口,实际返回的是RealCall
对象,将OkHttpClient、request传给了RealCall
,最后一个参数默认是false,表示是否是webSocket连接。第四步是调用了Call
的enqueue
方法,直接看RealCall
的enqueue
方法。
RealCall.enqueue
@Override public void enqueue(Callback responseCallback) {
//同步锁将executed置为true
synchronized (this) {
//如果是executed了,则直接抛异常
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.callStart();
//调用dispatcher的enqueue方法,并且将AsyncCall作为参数
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
上面代码很清晰,先判断是不是在executed过程,如果是executed则直接抛异常,否则走dispatcher的enqueue方法,并且传入了AsyncCall对象。
dispatcher.enqueue
void enqueue(AsyncCall call) {
synchronized (this) {
//首先添加到readyAsyncCalls集合里面
readyAsyncCalls.add(call);
//如果不是forWebSocket走这里,在构建RealCall的时候默认传入的false
if (!call.get().forWebSocket) {
//找是否有相同的host域名的请求call
AsyncCall existingCall = findExistingCallWithHost(call.host());
//如果有将存在的call对象的callsPerHost给当前的call对象
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
}
}
promoteAndExecute();
}
首先将AsyncCall
放到了readyAsyncCalls
集合里面,接着判断如果不是webSocket请求,将已经存在的callsPerHost给了当前call的callsPerHost,最后调用了promoteAndExecute
方法,findExistingCallWithHost很简单,就是通过running和ready集合里面比对host:
@Nullable private AsyncCall findExistingCallWithHost(String host) {
for (AsyncCall existingCall : runningAsyncCalls) {
if (existingCall.host().equals(host)) return existingCall;
}
for (AsyncCall existingCall : readyAsyncCalls) {
if (existingCall.host().equals(host)) return existingCall;
}
return null;
}
如果找到了调用call的reuseCallsPerHostFrom方法:
void reuseCallsPerHostFrom(AsyncCall other) {
this.callsPerHost = other.callsPerHost;
}
callsPerHost是一个int类型的原子类AtomicInteger,存储RealCall当前相同host的请求个数,后面会用到callsPerHost来限制详情host的请求数量。
dispatcher.promoteAndExecute
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
//用来存需要执行的call
List executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
//如果正在执行的call超过了maxRequests直接跳过
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
//相同host请求不能超过maxRequestsPerHost
if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
//从ready集合中移除该call
i.remove();
//对相同host原子对象+1
asyncCall.callsPerHost().incrementAndGet();
executableCalls.add(asyncCall);
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
//真正执行call的地方
asyncCall.executeOn(executorService());
}
return isRunning;
}
- 首先判断最大请求个数是否超过了
maxRequests=64
个和相同host是否超过了maxRequestsPerHost=5
个,这里面试中经常的一个考点。 - 如果都没到最大限制,则把当前的AsyncCall从readyAsyncCalls集合中移除掉,同时对RealCall的host个数+1操作,同时加到runningAsyncCalls和executableCalls集合中。
- 最后遍历executableCalls集合,执行AsyncCall的executeOn方法,这里要注意executorService方法是返回了一个线程池。所以最后执行任务的是在asyncCall的executeOn方法。
这里还有一个考点,在执行AsyncCall的时候是一个什么样的线程池:
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
知道线程池原理的小伙伴都知道,这里创建的线程都是非核心线程,并且最大线程池个数是无边界的,线程的等待时间是60秒。所以可以想象一下,OkHttp从刚开始有请求的时候,都是把任务先放到任务队列,然后开启对应个数的线程。如果在60秒内还有空闲的线程,则直接用空闲的线程处理任务,而不创建新的线程。
小节
到这里,我们先可以总结下:
- RealCall负责包装OkHttpClient、Request后,在同步的时候是直接交给了dispatcher的execute方法处理,在异步的时候new了一个AsyncCall,交给了dispatcher的enqueue方法处理。
- 在异步过程中先将AsyncCall添加到ready集合中,再判断非webSocket请求,然后将存在的AsyncCall存储的host数给当前的AsyncCall。
- 接着判断最大请求和相同host的个数,符合要求才添加到running和executable集合中。
- 最后遍历executable集合,通过new了一个线程池,开始调用AsyncCall的executeOn。
AsyncCall.executeOn
void executeOn(ExecutorService executorService) {
//通过线程池的execute方法执行RealCall
executorService.execute(this);
}
这里我把一些异常和finally中回调都给去掉了,留下了精简代码,直接通过线程池执行AsyncCall,而AsyncCall是继承自NamedRunnable抽象类,所以直接看它的run方法,run方法执行了execute方法,直接看AsyncCall的execute方法:
@Override protected void execute() {
boolean signalledCallback = false;
transmitter.timeoutEnter();
try {
//拿response的关键点
Response response = getResponseWithInterceptorChain();
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
//失败的回调
responseCallback.onFailure(RealCall.this, e);
}
} finally {
//执行完当前的AsyncCall处理
client.dispatcher().finished(this);
}
}
代码也都很简单,第一个是getResponseWithInterceptorChain方法拿response,第二个如果失败了回调给responseCallback,第三个是成功或失败完了后,调用dispatcher的finished,这里需要该方法:
dispatcher.finished
void finished(AsyncCall call) {
call.callsPerHost().decrementAndGet();
finished(runningAsyncCalls, call);
}
很简单,对当前AsyncCall存储的host数减一,接着调用了finished方法:
private void finished(Deque calls, T call) {
Runnable idleCallback;
synchronized (this) {
//从running中移除掉当前call
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
idleCallback = this.idleCallback;
}
//继续执行ready集合中还剩下的AsyncCall
boolean isRunning = promoteAndExecute();
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
上面finish方法首先将AsyncCall从runningAsyncCalls中移除掉,然后继续调用promoteAndExecute方法执行ready集合中没有完成的AsyncCall。说完了finish方法,我们回到上面的getResponseWithInterceptorChain调用:
getResponseWithInterceptorChain
从这个方法开始,我们进入到了拦截器部分,下面通过源码的方式介绍有哪几种拦截器:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List interceptors = new ArrayList<>();
//添加最原始的拦截器
interceptors.addAll(client.interceptors());
interceptors.add(new RetryAndFollowUpInterceptor(client));
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//添加netWork类型的拦截器
interceptors.addAll(client.networkInterceptors());
}
//添加真正联网请求的拦截器
interceptors.add(new CallServerInterceptor(forWebSocket));
//RealInterceptorChain真正处理拦截器连贯起来工作的类
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
//proceed是依次执行拦截器
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
- 首先添加
client.interceptors()
到interceptors集合中,紧接着添加RetryAndFollowUpInterceptor
、BridgeInterceptor
、CacheInterceptor
、ConnectInterceptor
、client.networkInterceptors()
,其中client.interceptors()
是通过OkHttpClient.Builder.addInterceptor
方法添加进来的,而client.networkInterceptors()
是通过OkHttpClient.Builder.addNetWorkInterceptor
方法添加进来的,它两都是集合,说明这两种拦截器是可以添加多个。 - 再来看下
RealInterceptorChain
的proceed
方法:
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
throws IOException {
//如果索引大于interceptors的size直接抛异常
if (index >= interceptors.size()) throw new AssertionError();
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
//获取到当前的拦截器
Interceptor interceptor = interceptors.get(index);
//调当前拦截器的intercept方法
Response response = interceptor.intercept(next);
return response;
}
- 首先判断index是否大于interceptors的size,接着将next+1传给了new的
RealInterceptorChain
,然后通过index获取到当前的interceptor
,最终调用了当前interceptor
的intercept方法,返回response,流程其实挺简单的,这里画了一张图:
未命名文件 (1).png
关于类似的图网上也有,这个是根据自己的理解画上整个拦截器工作图,在每一个拦截器里面通过上一个拦截器传入的
RealInterceptorChain
,在interceptor方法里面最终返回了RealInterceptorChain.proceed
,也就是上一个拦截器的response来自下一个烂机器的response。所以从这个拦截器的时序图来看,传入的是RealInterceptorChain
,传出的是response
。这里要注意下,面试中会问到有哪些拦截器,有开发者自己添加的最原始的拦截器(OkHttpClient.interceptors)、重定向拦截器(RetryAndFollowUpInterceptor)、桥接拦截器(BridgeInterceptor)、缓存拦截器(CacheInterceptor)、连接拦截器(ConnectInterceptor)、读写拦截器(CallServerInterceptor)、netWorkInterceptor拦截器,其中最原始的那个拦截器和netWorkInterceptor是okhttp暴露给开发者用的拦截器,其余的都是内置的拦截器。由于篇幅的原因,在这里主要看下CallServerInterceptor
内部interceptor方法:
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Exchange exchange = realChain.exchange();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
exchange.writeRequestHeaders(request);
boolean responseHeadersStarted = false;
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
exchange.flushRequest();
responseHeadersStarted = true;
exchange.responseHeadersStart();
responseBuilder = exchange.readResponseHeaders(true);
}
}
//真正网络请求的地方
Response response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
return response;
}
其他地方的代码都给去掉了,主要列出了请求的关键点,顺着exchange.connection()
可以找到最后okHttp是通过sockect请求的,有兴趣的朋友可以顺着这行代码看看,在这里就不带大家看了。关于其他内置的几个拦截器在这里就不讲了,后面专门拿一篇出来说这几个拦截器。
到这里拦截器的过程就讲得差不多了,我们回到上面okhttp给我们开发者自己实现的拦截器有addInterceptor方式添加和addNetworkInterceptor方式添加,下面我们着重说下这两个拦截器的区别:
addInterceptor(应用拦截器)和addNetworkInterceptor(网络拦截器)方式添加的拦截器区别
从上面拦截器时序图可以看出来,首先添加到interceptors集合的是通过OkhttpClient.Builder的addInterceptor方法添加进去的,而addNetworkInterceptor方式添加的拦截器是在ConnectInterceptor
拦截器之后之后添加的。可以看出来addInterceptor方式的拦截器是最原始的,而网络拦截器是经过了重定向拦截器,缓存拦截器。
-
应用拦截器
- 不需要关心像重定向和重试这样的中间响应。
- 总是调用一次,即使HTTP响应从缓存中获取服务。
- 监视应用原始意图。不关心OkHttp注入的像If-None-Match头。
- 允许短路并不调用Chain.proceed()。
- 允许重试并执行多个Chain.proceed()调用。
-
网络拦截器
- 可以操作像重定向和重试这样的中间响应。
- 对于短路网络的缓存响应不会调用。
- 监视即将要通过网络传输的数据。
- 访问运输请求的Connection。
-
应用场景
- 应用拦截器:如果对网络请求统一的加密或者添加统一的请求头或请求体,应用拦截器是不错的选择。
- 网络拦截器:如果需要监测请求的重定向、Connection、Accept-Encoding等详细信息就需要添加该监听器,比如facebook的Stetho框架就是通过添加
StethoInterceptor
拦截器实现网络监听。
总结
- OkHttpClient通过builder构建,可以添加应用拦截器、网络拦截器,设置读数据、连接的超时时间。
- Request通过builder构建,配置url、header、body等参数。
- 通过OkHttpClient的newCall方法并传入上面的Request对象,交给RealCall处理。
- 在RealCall的同步方法中直接添加到runningCalls集合中,接着就是调用了
getResponseWithInterceptorChain
获取response。 - 在异步方法中,构建了一个AsyncCall,并把它添加到ReadyCalls集合中,接着拿到相同host的Call把host数给当前的Call。
- 在开始执行AsyncCall前,先判断总请求数是否超过了64个和相同host请求数是否超过5个。如果都没超过,通过new的线程池来执行AsyncCall。
- 在执行AsyncCall的过程中,是通过
getResponseWithInterceptorChain
将所有的拦截器放到集合中,通过RealInterceptorChain
来连接每一个拦截器,最后通过上一个拦截器返回的response依次返回。
写到这okhttp流程算是分析完了,由于上家公司裁员,本人不幸是其中一个,因此如果有同行有android岗位可以推荐我,本人联系方式:[email protected]