上文已经说了,Okhttp和Retrofit是相辅相成的,Retrofit是对Okhttp的上层封装,Okhttp是Http的底层实现。那么,今天就来解析一下Okhttp的源码。
1 如何使用Okhttp
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.github.com/")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
Okhttp的使用如下,我们能够看到,先是new了一个OkhttpClient,然后这个OkhttpClient又new了一个Call,这个Call执行了enqueue方法,这个方法得到请求的结果,是成功或失败。
2 enqueue方法分析
我们看一下enqueue方法,发现Call是一个接口,最终在RealCall找到具体的实现,代码如下。
@Override 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));
}
我们可以看到,最终是dispatcher执行这个enqueue,那么我们就看一下这个diapatcher这个对象,再看一下它的enqueue方法。最终可以发现,是由executorService来执行call。而这个executorService传入的对象是Runnable,由此可以断定,Dispatcher这个类是用来做线程控制的。而且,逻辑代码应该是由某个Runnable接口的类来实现的。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
这个enqueue传入的对象是AsynCall,看一下AsyncCall里面的Runnable方法,发现具体的实现是在execute里面,execute实现如下。
@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);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
从里面可以看到,是网络相关逻辑的代码,并且对网络结果做处理。最重要的代码逻辑是在getResponseWithInterceptorChain()中。其实,这里面就是就是Okhttp的技术核心,也就是各种Interceptor的实现。Interceptor翻译成中文就是拦截器,同时,我们也可以自己加入拦截器。还能自己决定拦截器是放在开头还是结尾。
3 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));
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);
}
可以看到,先是创建了一个List,然后往里面丢了几个Interceptor。然后创建了一个Chain对象,调用了它的proceed方法。要想理解Okhttp的源码,最重要的就是要理解它的链式调用。
所谓链式调用,就是各个Interceptors会依次调用自己的intercept()方法,这里面会有三个步骤,先说第二步,第二步是调用chain.proceed()方法。把请求交给下一个Interceptor,调用它们的intercept()方法。那么第一步,就是调用下一个Intercepter的前置工作,第三步就是对返回的Response做后置工作。
简单的说,这个请求和响应就是一条链,首先A先对Request做自己的前置工作,然后交给B,B也对A传过来的Request做前置工作,然后交给C,C把Request发送到后台,后台返回Response,然后这个Response给B,B对这个Response做后置工作,然后返回A,A再对返回的Response做后置工作。如此,一次网络请求结束。
4 各个Interceptor的作用
既然已经清楚了Okhttp的链式调用,再来看一下各个拦截器的作用。
client.interceptors()和client.networkInterceptors(): 首先,这两个拦截器是没有具体逻辑的,需要我们自己去实现,我们可以通过addInterceptor(Interceptor)和ddNetworkInterceptor(Interceptor)添加我们自定义的拦截器,里面可以实现我们需要的功功能。 另外,它们是集合,就表示可以添加不止一个拦截器。无论是client.interceptors()还是client.networkInterceptors()都可以添加多个。它们具体的区别我会在后文中描述。
RetryAndFollowUpInterceptor: 负责重试和重定向的拦截器。这个是有一定条件的,比如你符合某个异常,像RouteException、IOException等,同时你又符合其它条件,比如是否允许重试等等。符合所有条件,才允许重试,像服务器返回404这种,是不会进行重试的。其实内部就是一个死循环,如果请求成功或不符合重试条件就跳出循环,释放资源。否则就继续循环。
BridgeInterceptor: 桥接的拦截器,主要是负责和Http相关的处理,比如添加 Content-Type、Content-Length 等请求头。同时,它还可以对gzip格式的包进行处理。
CacheInterceptor: 缓存的拦截器。获取缓存的网络数据是在交给下一个拦截器前,如果不需要网络获取并且缓存的Response不为空,就获取缓存数据。如果获取了缓存数据,后面的拦截器就不会再执行。
ConnectInterceptor: 连接的拦截器,里面只有前置工作,没有后置工作,因为连接都是建立在发送请求之前的。如果是Http会建立TCP连接,如果是Https在TCP之上还会建立TLS连接。
CallServerInterceptor:负责实际网络请求的I/O操作,里面主要是通过Okio的一些Api来实现。包括从Socket中读取数据和往Sokcket中写入数据
5 addInterceptor 和 addNetworkInterceptor的区别
这个是我们工作中经常会用到的,也是面试中问的比较多的问题,到底是选用addInterceptor还是选用addNetworkInterceptor呢。
Interceptor是在所有的拦截器之前,可以进行最早的前置工作,也可以做最后的善后工作,一般可以在这里面加入统一的Header。而NetworkInterceptor是在CallServerInterceptor之前,可以说是排在所有拦截器之后,除了真正进行网络操作的CallServerInterceptor。这里面看到的数据是原始数据,比如Body还没有被gzip解压等。
所以大多数情况下,我们会选用addInterceptor而不是addNetworkInterceptor。只有我们需要看原始数据的时候,我们才会需要使用addNetworkInterceptor。
至此,Okhttp的源码大致架构已经解析完毕,当然,里面还有更多具体的细节,就不一一描述了。