Android开发知识|结合源码谈一谈OkHttp解析
前言
Okhhtp是square公司出品的一款非常优秀的网络框架。可以说,目前市面上大部分Android开发人员在网络请求事,都在使用这款优秀的框架。下面,我就结合源码来分析下这款网络框架内部的执行原理,做到知其然也之气所以然的目的。
使用
OkHttp的使用非常的简单,我下面写一个大概的使用方法提供给大家查看。
//创建okhttpclient对象
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
//创建请求,里面可以设置get、post请求和请求地址和请求头等一系列操作
Request request = new Request.Builder()
.url("www.baidu.com")
.build();
//获取到一个call,这其实是一个线程请求任务,由其发起真正的网络请求
Call call = okHttpClient.newCall(request);
//同步请求
try {
call.execute();
} catch (IOException e) {
e.printStackTrace();
}//异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//TODO 异步请求失败的操作
}@Override
public void onResponse(Call call, Response response) throws IOException {
//TODO 同步请求成功的操作
}
});
可以看到,OkHttp的使用并不复杂,按照上面的一套流程下来就可以执行一次请求。由于这篇文章重点在于分析源码,所以对于OkHttp的使用并不做过多的描述。
下面开始进入分析源码状态。
Dispatcher分发器
dispatcher分发器可以说是OkHttp的重要部分之一,用一句话来说明他的用途,就是他是用来管理同步和异步请求的。
我们点击OkhttpClient的Builder()进去看下源码
public Builder() {
1dispatcher = new Dispatcher();
2protocols = DEFAULT_PROTOCOLS;
3connectionSpecs = DEFAULT_CONNECTION_SPECS;
4eventListenerFactory = EventListener.factory(EventListener.NONE);
5proxySelector = ProxySelector.getDefault();
6cookieJar = CookieJar.NO_COOKIES;
7socketFactory = SocketFactory.getDefault();
8hostnameVerifier = OkHostnameVerifier.INSTANCE;
9certificatePinner = CertificatePinner.DEFAULT;
10proxyAuthenticator = Authenticator.NONE;
11authenticator = Authenticator.NONE;
12connectionPool = new ConnectionPool();
13dns = Dns.SYSTEM;
14followSslRedirects = true;
15followRedirects = true;
16retryOnConnectionFailure = true;
17connectTimeout = 10_000;
18readTimeout = 10_000;
19writeTimeout = 10_000;
20pingInterval = 0;
}
可以看到在标记为1的地方有一个dispatcher = new Dispatcher(),原来分发器Dispatcher是在OkHttpClient的Builder中创建的。
然后点击Dispatcher进去看下Dispatcher类的内部代码
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private Runnable idleCallback;
/** 线程池 */
private ExecutorService executorService;
/** 准备执行任务的异步队列 */
private final Deque readyAsyncCalls = new ArrayDeque<>();
/** 正在执行任务的异步队列 */
private final Deque runningAsyncCalls = new ArrayDeque<>();
/** 正在执行任务的同步队列*/
private final Deque runningSyncCalls = new ArrayDeque<>();
}
删减了一些代码,我们只看他的内部的声明的一些变量。可以看到,在Dispatcher的内部其实声明了四个东西,三个任务队列,分为是:
准备执行任务的异步队列 readyAsyncCalls,
正在执行任务的异步队列 runningAsyncCalls,
正在执行任务的同步队列 runningSyncCalls;
和一个线程池 executorService;
那么Dispatcher是如何在执行同步和异步请求的呐?
同步请求
我们点击dispatcher.execute()的execute()方法进去查看源码
@Override
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
1.这段代码首先用一个同步锁synchronize来修饰代码块,代表这段代码只能是串行状态并不能支持并行。
2.其次再进行一次if判断,判断任务是否在执行,如果正在执行的任务再次进行执行,那么便会抛出异常"Already Executed",可见OkHttp不能重复进行任务的执行。
3.紧接着调用了dispatcher.execute(this)来执行同步任务
4.接着调用一个连接器链getResponseWithInterceptorChain(),将遍历所有的拦截器,最后将结果result返回。(关于拦截器后面再讲,这里不做论述)
5.最后再次调用dispatcher.finished(this)来结束所有的请求。
那么这个流程我们重点分析第3步和第5步
我们点击第3步的execute(this)方法里面查看里面做了什么
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
可以看到,这个方法仍然用synchronize来进行了修饰使得多线程也只能串行。之后将RealCall这个任务添加放入runningSyncCalls这个运行时同步任务队列中。
可以看出,execute()主要作用就是将任务添加到任务队列当中去, 那么添加进去之后在哪里执行呐?这就要到第5步的dispatcher.finish(this)方法中去看了。
点击finish(this)进入里面查看代码
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}private void finished(Deque calls, T call, boolean promoteCalls) {
1int runningCallsCount;
2Runnable idleCallback;
3synchronized (this) {
4if (!calls.remove(call)) throw new AssertionError("Call wasn'tin-flight!");
5if (promoteCalls) promoteCalls();
6runningCallsCount = runningCallsCount();
7idleCallback = this.idleCallback;
8}9if (runningCallsCount == 0 && idleCallback != null) {
10idleCallback.run();
}
}
具体我们需要看的是finished(Deque calls, T call, boolean promoteCalls)
前面几行都只是做一些判断,我们重点看第5行的代码,里面有一个判断if(promoteCalls) promoteCalls();
如果promoteCalls为true,那么就去执行promoteCalls()方法。那么重点再promoteCalls()方法里面到底做了什么。我们再点击进去看看
private void promoteCalls() {
1if (runningAsyncCalls.size() >= maxRequests) return;
2if (readyAsyncCalls.isEmpty()) return;
3for (Iterator i = readyAsyncCalls.iterator();
i.hasNext();
) {
4AsyncCall call = i.next();
5if (runningCallsForHost(call) < maxRequestsPerHost) {
6i.remove();
7runningAsyncCalls.add(call);
8executorService().execute(call);
9}10if (runningAsyncCalls.size() >= maxRequests) return;
}
}
可以看到前面1,2两步都是在判断,判断运行时异步队列的最大值是否超过64,超过则return结束掉。如果异步准备队列是空(isEmpty()),那么也return结束掉。
第3步开始进行for循环,从等待队列中取出任务,添加到运行队列runningAsyncCalls中去(runningAsyncCalls.add(call); ),然后再把call任务交给线程池去执行(executorService().execute(call)),从而完成一次请求。
那么来总结下同步请求:
通过dispatcher分发器,将一次网络请求call添加到运行时同步队列runningSynCall中去,然后再通过一个promoteCall()方法,把任务队列中的任务交给线程池去执行,从而完成一次请求。
异步请求
我们再来看下异步请求
//异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//TODO 异步请求失败的操作
}@Override
public void onResponse(Call call, Response response) throws IOException {
//TODO 同步请求成功的操作
}
});
我们点击enqueue()方法进去看下代码
void enqueue(Callback responseCallback);
看到只有一行代码,很简洁
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
我们再次跳到 client.dispatcher().enqueue(new AsyncCall(responseCallback)); 的enqueue()方法中去看下
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
可以看到,在这里面我们进行了一次判断,如果正在运行的异步队列里面的任务小于maxRequests并且runningCallsForHost(call) < maxRequestsPerHost则往runningAsyncCalls 中添加任务,然后交给线程池去执行,不然则往异步准备队列中添加任务,作为等待。
那么maxRequests和maxRequestsPerHost是啥?我们在分析Dispatcher的时候看到在这个类初始化了几个变量其中就有这两个变量
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
}
看到maxRequests为64,代表最大异步请求数量为64,最大的host数为5个,如果运行任务队列中超过了这两个数量,则不能把任务交给线程池去执行,而是直接放到readyAsyncCalls队列中去等待。
@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 {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
可以看到这里仍然通过一次连接器链将结果返回;
然后有几次判断,判断是否是需要重定向重连等,如果retryAndFollowUpInterceptor.isCanceled()代表连接取消失败,则回调responseCallback.onFailure(RealCall.this, new IOException("Canceled"));如果成功则回调responseCallback.onResponse(RealCall.this, response);
最后又回到了finish(this)方法,这个之前说过了,就是把运行队列中的内容拿出来交给线程池去执行。
总结一下异步请求流程
异步请求
1.一开始会去执行dispatcher.enqueue(),里面会有两个判断,判断运行任务列队runningAsyncCalls中的任务总数是否超过64个以及网络请求的host是否超过5个,如果没超过这两个数,那么往运行任务队列中添加任务,然后把任务交给线程池去执行。如果超过了,则直接放到准备等待任务队列中readyAsyncCalls中去等待。
2.之后执行一次拦截器链getResponseIntecepterChain()把结果返回返回
3.在进行一次判断,这个任务是否需要重新连接,是否被取消retryAndFollowUpInterceptor.isCanceled(),如果取消了则证明这个任务执行失败了,则在线程中回调onFailure()方法,如果成功则回调onResponse()方法。
4.最后调用dispatcher.finish(this)方法,里面有一个promoteCalls()方法,用来调整队列中的任务。通过把刚才准备队列中(readyAsyncCalls)中的任务再次添加到异步运行任务队列中去,然后交给线程池去执行。
这就是一次异步请求的流程。
OkHttp的拦截器
如果说OkHttp最精华和亮点的功能是啥的话,我会毫不犹豫的说是拦截器功能。拦截器可以说是整个OkHttp最精华不可不说的功能。
什么叫做拦截器?
我的个人理解是,在你进行发起一次真正意义上的网络请求时,需要进行操作或者闯过的一些关卡,只有通过了这个闯关之后,才真正的能够发起一次真正意义上的网络请求。
【Android开发知识|结合源码谈一谈OkHttp解析】我们来看一次完整意义上的拦截器的请求流程是如何实现的。
@Override public Response execute() throws IOException {
synchronized (this) {
1if (executed) throw new IllegalStateException("Already Executed");
2executed = true;
}
captureCallStackTrace();
try {
3client.dispatcher().executed(this);
4Response result = getResponseWithInterceptorChain();
5if (result == null) throw new IOException("Canceled");
6return result;
} finally {
7client.dispatcher().finished(this);
}
}
我们把代码放到RealCall这个类中,从这个类中找到execute()这个方法,当然这里面有两个execute()方法,一个是异步请求的,一个是同步请求的。我们以同步请求为例(其实异步在拦截器这块是一样的)。
看到在被标记起来的第三行,分发器dispatcher先执行executed(this)同步方法,然后在第四行会有一个叫做getResponseWithInterceptorChain() 的方法,执行了这个方法之后获取到结果,然后将result结果返回即完成。而getResponseWithInterceptorChain() 就是执行拦截器链的主要核心方法。我们下面重点来看这个方法具体做了哪些操作。
getResponseWithInterceptorChain():
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List 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);
return chain.proceed(originalRequest);
}
点击进入这个方法,我们一步一步来看。
首先是创建了一个集合interceptors,这个List集合的泛型就是Interceptor,即拦截器类型。也就是说,这个集合是专门用来存储拦截器的。
之后便是一大堆的add(),即往这个集合中不断的按照顺序添加拦截器。
我们看到,
添加的有自定义的普通拦截器client.interceptors();
重试或重定向拦截器retryAndFollowUpInterceptor;
设置一些请求头、压缩数据和设置一些cookie等操作的BridgeInterceptor拦截器;
设置缓存或者使用缓存的缓存拦截器CacheInterceptor;
复用联接操作里面有一个连接池的链接拦截器ConnectInterceptor
在发起一次真正网路请求之前可以做一些自定义操作的另外一个自定义拦截器networkInterceptors
真正进行一次网络请求的请求拦截器CallServerInterceptor
在添加完所有拦截器到集合之后,用将这个集合放入到一个类RealInterceptorChain中,最后执行这个RealInterceptorChain对象的一个process()方法,并且把结果返回。
这便是一次完整的拦截器过程。
那么看完上面这个流程很多人是否会云里雾里的摸着脑袋说,前面的创建集合我看懂了,把拦截器分批放入到集合中我也看懂了,但是最后把集合放入RealInterceptorChain这个类的目的是啥和执行process()是为了干嘛我就没看懂了。也不晓得,为啥把拦截器放入到集合中就能执行了,他是怎么一层一层执行拦截器的我也不知道。
说实话,我一开始看这段代码也是只看得懂前面定义集合和把拦截器放入集合这操作,后面的操作真的就是小朋友你是不是有好多问号的状态。
那我们来一个一个回答上面的几个问题。
第一个问题,为啥要把装了拦截器的集合放入到一个叫RealInterceptorChain的类的构造方法中去?
我们点击进去到这个类里面,看看源码。
public final class RealInterceptorChain implements Interceptor.Chain {
private final List interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private int calls;
public RealInterceptorChain(List interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
} }
由于这个类的代码比较多,我这里先贴出部分代码,我们一步一步拆解来看。
可以看到,这个类一开始定义了好多个变量,比如 List、StreamAllocation、HttpCodec 、RealConnection 等等。而你之所以定义这个变量的目的,肯定是因为之后需要用到这个量嘛,那具体在哪里用到呐,我们后面再讲。那既然要用到这些变量,那我们肯定要给他赋初始值,不然你咋操作呢?那这些初始值哪里来呐?那不就是我们通过构造方法传进去么。就像我们常常定义一个JavaBean一样,也会在构造方法中传入值来进行变量赋值,所以这里远离也是一样,我们后面在这个类RealInterceptorChain的某个方法中需要操作到List中拦截器,所以先在构造方法中传进来。
这也就解释了第一个问题,为什么要把添加了拦截器的集合List传入到RealInterceptorChain中,因为这个类里面有需要用到操作这些拦截器的方法,所以需要先传值进来。
第二个问题,定义了那么多拦截器,是如何一层一层进行调用的?
回答这个问题,我们需要看后面的一个方法proceed(),正是在这个方法里面执行操作的。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}return response;
}
这段代码,前面都是三个if判断,并且抛出一些相应的异常信息,这是为了做一些容错处理,没啥看的。我们看主要部分。
1 RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
2 Interceptor interceptor = interceptors.get(index);
3 Response response = interceptor.intercept(next);
这是proceed()方法中最主要的三行代码。
第一行代码看到,我们这边又实例化了一次RealInterceptorChain类,而且传入的参数几乎都是一模一样的,只是在index的时候传入的是(index + 1),其他都没改变
第二行则是从拦截器的List集合中通过get()方法拿出index位置的拦截器
第三行则是第二步拿出的拦截器去执行intercept()方法,也就是执行这个拦截器。
不知道大家看着三行代码有没有看晕,反正我最早看是晕了,又一次不晓得他到底在干嘛,为啥又要实例化一次RealInterceptorChain,传入(index + 1)又是什么鬼。
造成这么晕的原因是因为我们孤立的看了这三行代码,如果仅仅只是看着三行代码而不联系之前的RealInterceptorChain类的构造方法的解释来看确实是看不懂的。
首先,我们之前分析过之所以要把拦截器的List集合传入到RealInterceptorChain类的构造方法的目的是因为在RealInterceptorChain类里面有某个方法使用到了拦截器集合,那么是哪个方法呐?就是现在的这个proceed()方法。
而我们现在不断的实例化这个RealInterceptorChain类的目的就是为了不断的更新这个构造方法中的变量,那么这里面哪个变量是不断被更新的呐?就是这个index。
我们不断的传入(index + 1)的目的就是不断的让这个index自增,即index = index + 1。
那么是无限自增下去么?显然不是,他是有条件的自增的,条件就是proceed()方法中的第一个if判断
if (index >= interceptors.size()) throw new AssertionError();
当index自增到超过了拦截器集合里面最大数量,即size()的时候他就不能自增而是抛出异常了。
这里就解释了为啥要在prceed()方法中不断的再次new RealInterceptorChain()这个类,原因就是为了不断的更新index这个变量,让他不断的自增 +1
再来看第二行代码
Interceptor interceptor = interceptors.get(index);
这行代码因为任何人都能看的懂吧,就是从集合interceptors中获取下角标为index的值,也就是说获取集合中第index位置的拦截器。
那么结合第一行解释的,通过第一行不断的更新了index的值,使得其不断的自增+1,目的就是为了能够给第二行interceptors.get(index)去使用这个值。让集合能够不断的取出下一个拦截器来。
比如,第一行代码更新的index,让其自增了+1,即index = index + 1,那么到了第二行使用的不再是index这个值,而是变成(index + 1)这个值,再配合一开始的if判断给index的自增添加一个边际,从而达到遍历取出List集合中拦截器的目的。
最后是第三行代码,这行很简单,从集合中拿出了拦截器直接去执行就OK了。
总结一下proceed()方法
这个方法目的其实说白了就是不断的遍历拦截器集合,从集合中取出拦截器来,然后通过intercept()方法,不断执行下一个拦截器,从而达到链式调用的目的。
到此,整体拦截器的调用步骤完成。
后面将分开写一些主要拦截器和自定义拦截器的内容和源码分析。
RetryAndFollowUpInterceptor拦截器
这个叫做请求重定向拦截器,主要的作用是在请求失败之后,自动的进行重新连接的操作。
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
1 streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
2 int followUpCount = 0;
3 Response priorResponse = null;
4 while (true) {
5if (canceled) {
6streamAllocation.release();
7throw new IOException("Canceled");
8}9Response response = null;
10boolean releaseConnection = true;
11try {
12response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
13releaseConnection = false;
14} catch (RouteException e) {
15if (!recover(e.getLastConnectException(), false, request)) {
16throw e.getLastConnectException();
17}
18releaseConnection = false;
19continue;
20} catch (IOException e) {
21boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
22if (!recover(e, requestSendStarted, request)) throw e;
23releaseConnection = false;
24continue;
25} finally {
26if (releaseConnection) {
27streamAllocation.streamFailed(null);
28streamAllocation.release();
29}
30}31if (priorResponse != null) {
32response = response.newBuilder()
33.priorResponse(priorResponse.newBuilder()
34.body(null)
35.build())
36.build();
37}38Request followUp = followUpRequest(response);
39if (followUp == null) {
40if (!forWebSocket) {
41streamAllocation.release();
42}
43return response;
44}45closeQuietly(response.body());
46if (++followUpCount > MAX_FOLLOW_UPS) {
47streamAllocation.release();
48throw new ProtocolException("Too many follow-up requests: " + followUpCount);
49}50if (followUp.body() instanceof UnrepeatableRequestBody) {
51streamAllocation.release();
52throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
53}54if (!sameConnection(response, followUp.url())) {
55streamAllocation.release();
56streamAllocation = new StreamAllocation(
57client.connectionPool(), createAddress(followUp.url()), callStackTrace);
58} else if (streamAllocation.codec() != null) {
59throw new IllegalStateException("Closing the body of " + response
60+ " didn't close its backing stream. Bad interceptor?");
61}62request = followUp;
63priorResponse = response;
}
}
这些代码本身不做具体分析,没有啥太难懂的地方,但是这里我需要提醒大家注意的一点就是重新连接的次数并非是无限次的,他其实是有一个最大值的。在第46行,我们可以看到
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
这里有一个if判断,当followUpCount > MAX_FOLLOW_UPS的时候则会停止从重连,抛出异常。那么这个MAX_FOLLOW_UPS是多少呐?
private static final int MAX_FOLLOW_UPS = 20;
可以看到,这个最大重连次数是20次,也就是说,RetryAndFollowUpInterceptor拦截器的重连次数并非无限,而是最多20次,超过了这个次数则不再进行重新连接了。
BridgeInterceptor拦截器
这个拦截器可以称之为桥接拦截器。代码如下:
@Override 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 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);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}return responseBuilder.build();
}
这个拦截器的主要功能是在设置一些请求时候的头部信息功能,例如设置"Content-Type"、"Content-Length"、"Host"、"Cookie"等。
结合CacheInterceptor缓存拦截器来谈一谈OkHttp的内部缓存原理
OkHttp的缓存过程是一个很值得细讲的过程。那么我们这里配合着缓存拦截器来谈一谈OkHttp的缓存内部原理。
如何使用缓存?
要想使用OkHttp的缓存,其实可以在初始化OkHttpClient的时候设置好。
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(new Cache(new File("缓存位置"),1024))
.build();
可以看到,在这里有一个cache()的构造方法,里面有一个参数,实例化一个Cache的类,这个类就是用于缓存的类。
我们点击进入这个类里面查看构造方法
public Cache(File directory, long maxSize) {
this(directory, maxSize, FileSystem.SYSTEM);
}
从构造方法看,这个构造方法有两个参数,一个是用来记录缓存的位置的File,一个是用来记录缓存大小的maxSize。通过这两个参数可以将缓存存放在相应的位置并设置好缓存的大小。
之后我们进入到Cache这个类中看到,这个类中最主要的方法有两个,一个是获取缓存的get()方法,一个是存储缓存的put()方法。我们分为来看看这两个方法。
缓存存放方法put()
先来看存储方法put()
1 CacheRequest put(Response response) {
2String requestMethod = response.request().method();
3 if (HttpMethod.invalidatesCache(response.request().method())) {
4try {
5remove(response.request());
6} catch (IOException ignored) {
7}
8return null;
9}
10 if (!requestMethod.equals("GET")) {
11return null;
12 }13 if (HttpHeaders.hasVaryAll(response)) {
14return null;
15 }16 Entry entry = new Entry(response);
17 DiskLruCache.Editor editor = null;
18 try {
19editor = cache.edit(key(response.request().url()));
20if (editor == null) {
21return null;
22}
23entry.writeTo(editor);
24return new CacheRequestImpl(editor);
25 } catch (IOException e) {
26abortQuietly(editor);
27return null;
28 }
}
从代码可以看到,这个存储方法前面都是一些if判断,主要功能就一个,去除掉不是GET请求的方式,也就是说OkHttp他的缓存只缓存GET请求,对于POST等其他请求他是不进行缓存的。遇到其他请求方式,直接返回null
我们从16行开始看起,这里会初始化一个Entry数组,这个数组就是用来写入一些缓存信息。
之后会通过cache.edit()方法,把我们请求的url作为key来获取一个DiskLruCache类。也就是说,OkHttp的缓存是基于DiskLruCache算法来进行存储的,用请求的url作为key来查询结果。
最后是通过 entry.writeTo(editor)这个方法,把缓存结果写入到entry数组当中去,从而完成一次缓存。
缓存获取方法get()
我们再来看缓存获取方法
Response get(Request request) {
1 String key = key(request.url());
2 DiskLruCache.Snapshot snapshot;
3 Entry entry;
4 try {
5snapshot = cache.get(key);
6if (snapshot == null) {
7return null;
8}
9 } catch (IOException e) {
10return null;
11 }12 try {
13entry = new Entry(snapshot.getSource(ENTRY_METADATA));
14 } catch (IOException e) {
15Util.closeQuietly(snapshot);
16return null;
17 }18 Response response = entry.response(snapshot);
19 if (!entry.matches(request, response)) {
20Util.closeQuietly(response.body());
21return null;
22 }23 return response;
}
首先,先通过请求的url获取key值,之后在DiskLruCache算法中有一个叫做Snapshot的对象,这个对象叫做缓存快照,作用就是用来记录某一时刻的缓存。
之后通过key获取到这一时刻的缓存快照snapshot,此处再做一次判断,如果为空则代表此时没有缓存,返回null,否则通过此时获取的缓存快照从Entry数组中获取此刻的缓存结果(前面也提到了DiskLruCache缓存最后是写入到Entry中的)
最后获取到了缓存之后并没有结束,还有一层判断
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
这里是判断我们获取的缓存是否和我们请求时的缓存是匹配的。因为要知道,我们最终获取的缓存是必须是我们曾经请求时候缓存下来的,如果不匹配,则代表这个获取的缓存并非是我们曾经发起网络请求时候的缓存,则没必要返回。而如果是matches匹配上了,则代表是一个缓存。
到此,缓存的存储和获取的方法分析完毕。那么问题又来了,OkHttp是在哪里调用这个获取和存储缓存的两个方法的呐?那就是接下来要重点分析的缓存拦截器的功能了。
缓存拦截器——CacheInterceptor
@Override public Response intercept(Chain chain) throws IOException {
1 Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
2 long now = System.currentTimeMillis();
3 CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
4 Request networkRequest = strategy.networkRequest;
5 Response cacheResponse = strategy.cacheResponse;
6 if (cache != null) {
7cache.trackResponse(strategy);
8 }9 if (cacheCandidate != null && cacheResponse == null) {
10closeQuietly(cacheCandidate.body());
}11 if (networkRequest == null && cacheResponse == null) {
12return 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();
}13if (networkRequest == null) {
14return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
15 }16 Response networkResponse = null;
17 try {
18networkResponse = chain.proceed(networkRequest);
19 } finally {
20if (networkResponse == null && cacheCandidate != null) {
21closeQuietly(cacheCandidate.body());
22}
23 }24 if (cacheResponse != null) {
25if (networkResponse.code() == HTTP_NOT_MODIFIED) {
26Response response = cacheResponse.newBuilder()
27.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
28networkResponse.body().close();
29cache.trackConditionalCacheHit();
30cache.update(cacheResponse, response);
31return response;
32} else {
33closeQuietly(cacheResponse.body());
34}
35 }36 Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
37 if (cache != null) {
38if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
39CacheRequest cacheRequest = cache.put(response);
40return cacheWritingResponse(cacheRequest, response);
41}42if (HttpMethod.invalidatesCache(networkRequest.method())) {
43try {
44cache.remove(networkRequest);
45} catch (IOException ignored) {
46// The cache cannot be written.
47}
48}
49 }50 return response;
51 }
这是缓存拦截器主要执行的代码。可以看到在第三行,有一个叫做缓存策略的类CacheStrategy,而这个类可以说就是整个缓存拦截器的核心,他决定了到底是使用缓存还是使用网络连接去获取数据。
我们点击进去看下这个类内部。
public final class CacheStrategy {public final Request networkRequest;
public final Response cacheResponse;
CacheStrategy(Request networkRequest, Response cacheResponse) {
this.networkRequest = networkRequest;
this.cacheResponse = cacheResponse;
}public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
return new CacheStrategy(null, null);
}return candidate;
}
}
这是部分代码。我们可以看到这里面定义了两个类,一个是Request,一个是Response。然后通过get()方法中的getCandiate()来获取到缓存策略类CacheStrategy,之后就根据不同的条件来操作这个类,看看到底是用缓存还是用网络请求。
我们来看下到底哪些条件下用缓存,哪些条件下又用网络请求。
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();
}
当判断网络请求为空(也就是没有网络)并且缓存也为空的时候,那么就直接抛出去一个504的响应码,代表网关超时
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();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
当网络的缓存不为空,并且请求码是networkResponse.code() == HTTP_NOT_MODIFIED,也就是304未修改的时候,我们直接从缓存中取出数据来处理。此时用到了缓存。
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
而这里可以看到,在使用网路请求获取到数据之后,会调用cache.put(response)将数据直接存入到缓存中,这里我们之前的代码分析过内部是如何存储的,这里不在多说。
到此,整个缓存拦截器就算全部写完了。
总结一下缓存拦截器
缓存拦截器内部其实是依靠一个缓存策略的类来控制到底是否使用缓存。如果在既没有网络也没有缓存的情况下,他会直接抛出504的网关超时的警告,之后判断如果请求未修改,也就是304的时候则直接从缓存中获取缓存信息返回。如果没有缓存,则先通过网络端获取缓存,之后将缓存存储,方便下次使用。
推荐阅读
- android第三方框架(五)ButterKnife
- 知识
- 深入理解Go之generate
- Android中的AES加密-下
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- 小学英语必考的10个知识点归纳,复习必备!
- 带有Hilt的Android上的依赖注入
- android|android studio中ndk的使用
- 生发知识,带你深入了解
- Android事件传递源码分析