在本系列的上一篇文章中,咱们走读了一遍okhttp的源码,开始了解了这个强大的网络框架的根本履行流程。

不过,上一篇文章只能说是比较粗略地阅览了okhttp整个履行流程方面的源码,搞了解了okhttp的根( / v本工作原5 } V . P u = M 9理,但并没有去深入剖析细节(事实上也不行能在一篇文章中深入剖析每一处源码的细节)。那么本篇文章,咱们对okhttp进行深入地剖析,慢慢将okhttp中的各项功用进行全面把握。

今天文章中的源码都建在上一篇源码剖析的基础之D ? V上,还没有看过上一篇文章的朋友,主张先去阅览 网络恳求框架OkHttp3全* = W m J B解系列 – (二)OkHttp的工作流程剖析 。这篇中咱们知道,网络恳求的真实履行是经过拦截器链相关的各个拦截器进行处理,每个拦截器负责不同的功用,下面将详细剖析每个拦截器,包含重要知识点——缓存、衔接池。

先回忆下RealCall的getResponseWithInterceptorChain办法——拦截器的添加以及拦截器链的履行:

  Response getResponseWithInterceptorChain() throws IOException {
// 创立一系列拦截器
List<Int_ b $ w H _ w ; (erceptor> interceptors6 M p = new ArrayList&D B R U . ;lt;>();
interceptors.addAll(client.interceptors());
interceptors.add(new RetryAndFollowUpInterceptor(client));
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new C) r | 3 B p S ,acheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceps X ctor.Chain chain = new RealInterce9 - u o @ $ ? 2ptorg 2 e } | # 9Chain(interceptors, transmitter, null,f t { 6 b e 0,
origib h g $ F ] 6 * mnalRequest, this, client.connectTimeoutMilR @ r _ Zlis(),
client.readTimeoutMillis(), client.writ@ e peTimeoutMillis());
...
//拦截器链的履行
Response responW ( J ] & bse = chain.proceed(originalRequest);
...
return response;
...
}

RetryAndFollowUpInterceptor – 重试、重定向

假如恳求创立时没有添加应用拦截器,那么第一个拦截器便是RetryAndFollowUpInterceptor,意为“重试和跟进拦截器”,效果是衔接失利后进行重试、对恳求成果跟进后进行重定向。直接看它的intercept办法:

  @Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChV u L F Q 4 t x !ain = (RealIntercept= 2 1 S Y 5 SorChain) chain;
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
Response priorResponse = null;
while8 0 o [ E E (true) {
//预备衔接
transmitter.prepareToConnect(request);
if (transmitter.O & ~isCanceled())] w S ; # f {
throw new IOExcepO 7 j tion("Canceled");
}
Resr K * y b _ponse responsep ( u;
boolean success = false;
trD p i S o g #y {
//持续履行下一个Interceptor
response = realChain.proceed(request, t, V  z rransmitter, null);
success = td z d - Irue;
} catch (RouteExceptioU 2 C ^n e) {
// 衔接路由反常,此刻恳求还未发送。测验康复~
if (!recovM a V m ] % W E Zer(= P Je.getLastConnectException(), transmitteQ p x er, false, re? g 5 p Y RquestG ! 9 ) 4)e z ) # N C # 6 () {
throw e.getFirstConnectException();
}
continue;
} catch (IOException e) {
// IO反常,恳求或许现已发出。测验康复~
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, transmG T G !itter, requestSendStarted, requey E ? 1 `st)) throw e;
continue;
} finally {
// 恳求没成功,开释资源。
if (!success) {
transmitter.o 3 * ^ T LexchangeDoneDueToException();
}
}
// 相关上一个response
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.a [ i =neI @ , S 1 ! _ 6 DwBuilder()
.body(null)
.build())
.build();
}
Exchange exchange = Internal.instance.exchange(response);
Route route = exchange != null ? exchange.connection().route()& e n y c : null;
//跟进成果,4 z X B l g Z首要效果是依据呼应码处理恳求,回来Request不为空时则进行重定向z | H H V 9 / )处理-拿到重定向的request
Request followUp =! H o 8 I followUpRequest(response, route)W 9 ! , | t C { :;
//- g k |fo3 7 Q c y G d lllowUp为空,不需求重试,直接回来
ifn x P ! } 6 r m B (followUp == null) {
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEar# U s = g ; ]lyExit();
}
returV Q ) [ Z D p Pn response;
}
//followUpBody.isOneShot,不需求重试,直接回来
RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(responsY 4 Y Ze.body());
if (transmitter.hasExchange()) {
exchange.detacQ w T Z }hWithViolence();
}
//最: # s z多重试20次
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too manyl 0 [ [ 6 F = ; follow-up requests: " + followUpCount);
}
//赋值,以便再次恳求
request = followUp;
priorResponse = response;
}
}

运用while进行循环

先用transmitY Z * O wter.prepareToConnect(request)进行衔接预备。transmitter在上一篇有说到,意为发射器,是应L $ f G : 5 % P a用层和网络层的桥梁,在进行 衔接、真? E } A ! 2 4 3 d实发出恳求和读取呼应中起X j x ~ q A U { @到很重要的效果。看下prepareToConnect办法:

  public void prepareToConnect(Request request) {
if (this.request != null) {
if (sameConnectionU E K(this.reqJ e v puest.url(), request.url()) &v G n 4 Y ;& exchangeFinder.hasRouteToTry()) {
r_ @ P veturn; //已有相同衔接
}
...
}
this.request = request;
//创立ExchangeFinder,意图是为获取衔接做预备
this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
ca` N o o wll, eventListeneJ o h O zr);
}

Z _ ` i z q ? b N要是创立ExchangeFinder整理赋值给transmitter.exchangeFinder。ExchangeFinder是G o D @m q w流查找器,效果是获取_ y ` p 4 X恳求的衔接。这儿先了解下,后面会详细阐明。

接着调用realChain.proceed 持续传递恳求给下一个拦截器、从下一个拦截器获取原始成果。假如此过程发生了 衔接路由反常 或 IO反常,就会调用recover判别是否进行重试康复。看下X g V / – @recover办法:

  p* Y q K Lrivate boolean recover(IOException e, Transmitter transmitter,
boolean rY C f s d * : w RequestSenK 7 | $ J @ ld[ Q BStarted, Request uK { 2serRequest) {
// 应用层禁止重试,就不重试
if (!client.retryOnConnectionFailure()) return false;
// 不能再次发送恳求,就不重试
ifD v 8 ; | G a W (requestSendStarI R k 3 - . 2 B +ted &a] ] j Omp;& requestIsOneShot(e, userRequest)) return f2 t M / m { M f ^alse;
// 发生的反常是致命的,就不重试
if (!isRej V ] f :  Acoverable(e, requestSendStarted)- A C) return false;
// 没有路: W ) Y } ^ & n由能够测验,就Y t K不重试
if (x B @ a l!transmitter.canRetry()) return falseZ l M O 8 U x J;
return true;
}

假如recover办法回来true,那么就会进入下一次循环,重新恳求。

假如realChain.proceed没有发生反常,回来了成果response,就会运用followUpRequest办法跟进成果并构建重定向request。 假如不必跟进处理q q / O p(例如呼应码是200),则回来null。看下followUpRequest办法:

  private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException {
...
int responseCode = up 1 | 6 ~ serRespo$ % U j K 5 | h hnse.code(] Q h c V P ) ] :);
...
switch (responseCode) {
...
case HTTP_PERe p 8 O ) oM_REDIRECT:
case HTTP_TEMP_REDS L ] b % ^ dIRECT:
if (!method.equals("GET") &amS 8 z 7 B A X rp;& !method.eq: ^ L o - @ . w Buals("HEAD")) {
return null;
}
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HM  * u /TTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
if (!client.followRedirects()b - R j k . 5) return null;
String location = userResponse.header("Loca= p Z ) @tion");
if (location == null) return null;
HttpUrl url = user| W ! JResponse.request(( / e p o B W b R).urW j A N P M g Ll().resolve(location);
if (url == null) return null;
boolean sameScheme = url.scheL % 8 kme().equals(userResponse.request().url().scheme(V m / t k));
if (!sameScheme && !client.followSslRedirects()) return null;
Request.Builder requestBuilder = userR9 S e U d pesponse.request().newBuilder();
if (1 2 _ } a 0HttpMethodd  A X T _ h.permitsRequestBody(method)) {
final boolean maintainBody = Htt} E [ z v T ipMethodQ J p D n V.r1 @ N F redireci  h t = =tsWithBody(method);
i2 L Af (HttpMethodg M 7.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} elsM H 5 ` , y [ : Fe {
RequestBody requestBody = maint$ B _ : _ 4ainBody ? usB T ( + m J I ;erResponse.request().body() : null;
requestBuilder.method(method, requestBody);
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding");
requestBuilderR U D 8 - h 4 u.removeHeader("Content-LengthQ o ~ S C");
requestBuilder.removeHeader("Content-Type");
} _ n 9 7 E -
}
if (!sameConnection(userResponse.request().url(), url)) {
requestBuilder.removeHeader("Authorization");
}
return requestBuilder.url(url).bue ) ^ B t N t {ild();
case HTTP_CLI$ 6 mENT_TIMEOUT:
...
default:
return null;
}
}

首要4 I G % B f 2 H便是依据呼应~ J } N v p @ 0码判别假如需求重定向,就从呼应中取出重定向的url并构建新的Requ; 4 ` ;est并回来出去。

往下看,还有个判别:最多重试20次。到这儿,RetryAndFollowUpInterceptor/ D Y % |就讲完了,仍是比较简单的,首要便是衔接失利的重试、跟进处理重定向。

BrJ a { | 6idgeInterceptZ / # 1 + Cor – 桥接拦截器

接着是 Brij D N l ! G edgeInterceptor ,意为 桥拦截器,相I h y I当于 在 恳求建议端 和 网络履行端 架起一座桥,把应用层发出的恳求 变为 网络层认识的恳求,把网络层履行后的呼应 变为 应用层便于应用层运用的成果。 看代码:

//桥拦截器
public final class BridgeInterceptor implements Interceptor {
//Cookie管理器,初始化Oo g h , * 2 # ,khttpt - * W j [ F Client时创立的,默许是CookieJar.NO_COOKIES
private final CookieJar cookieJar: c z j % # 3;
public BridgeInterceptor(CookieJar cookieJar) {
this.cookieJar = coou ! U N N s &kieJar;
}
@Overrido M R + = c |e public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.r8 y h m i gequest();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body._ = O a h hcontentType();
if (contentType != nu[ # |ll) {
requestBuilder.header("Content-Type", contentType.tok X x h QString());
}
long contentLength = body.contentLength();
//假如现已知道body的长度,就添加头部"Content-Length"
if (contentLength != -1) {
requv ( D i 2estBuilder.header("Content-Le Q O 3 Jngth", Long.toString(contentLength));
requG r GestBuilder? V ;.removeHeader("l 7  o r hTransfer-Encoding");
} eB V k ~ g $lse {u C g O = P , c
//假如不知道body的长度,就添加头部"Transfer-Encoding",代表这个报文采用了分块编码。这时,报文中的实体需求改为用一系列分块来传输。详细了解请参考:[HTTP Transfer-Encoding介绍](https://blog.csdn.net/Dancei 4 | [ A q un/article/details/89957486)
requestBu| G 8 F ( vilder.header(n 2 [ C B x g"Transfer-Encoding", "chunked^ 8 ( . o");
requestBuilder.remove1 4 7 sHeac J l P $ 8 9 6der("Content-Lengthx o 6 ) : p J l +");
}
}
if (~ U h W 1 0 ` Y Pusn l c - E 6 YerRequest.header("Host") == null) {
requestBuilder.hu Y N ~ Z _eader("HosX : 2 R ! m }t", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.heU N . P r B L zader("Connection", "Keep-r z 5 d E KAlive");
}
//"Accept-Encoding: gzip",表明承受:回来gzip编码紧O M y缩的数据
// } // 假如咱们手动添加了 "Accept-Encoding: gzip" ,那么下面的if不会进入,transparentGzipD j M是false,就需求咱们自己处理数据解压。
//假如 没有 手动添加"Accept-Encoding: gzip" ,transparentGzip是true,同时会主动添加,并且后面也会主动处了解压。
boolean transparentf G  {Gzip = false;
if (userRequest.header("Accept-Encoding") == null &amf {  J = O mp;& userRequest.header("Range") == null) {
transpP d ^ = u ) : n narentGzip = true;
requestBuilder.header("Accept-Encoding", "4 0 x ! }gzip");
}
//从cookieJar中获取cookie,添加到header
List<Cookie> cookies = cookieJar.loadForRequest(userReques4 o 3 0 Vt.ur? u y y R H ` Rl());
if (!cookies.isEmpty()) {
requestBuilder.heaR U B 6 Lder("Cookie", cookieHeader(cookiex A M v = H g #s)q k t U b O E 8 ~);
}
//"Ut x V c Tser-Agent"一般需求作为公共header外部统一添加
if (userRequest.hea_  ader("User-Agent") == nu[ 8 d o - 5ll) {
requestBuilder.header("User-Agent", Version.userAgent());
}
//处理恳求
Response networkResponse = chain.p G @ K m O D { 8proceed(requestBuilder.build());
//从networkResponse中获取 header "Set-Cookie" 存入cookieJar
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkRespon# W ` 9 ` / v |se.headers());
Response.Builder responseBuildx 1 s / !   0 fer = networkResponse.newBuilder()
.request(uk @ ) D * m [ tserRequest);
//假如咱们没有手动添加"Accept-Encoding: gzip",这儿会创立 能主动解压的responseBody--GzN n d o 8ipSource
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& Http~ k 7 # f  p /Headers.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body(3 6 w # M =).source());
HD ~ ` o 3 ! _ .eaders strippedHeaders = networkResponse.h) B u f Ceaders().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();( ; r } & Z J
responseBuilder.headers(stripped~ 6 9 + E fHeaders);
St^ ! A o m kring cQ k R q ; Y _ contentTy. l & e u E 7 spe = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
/s l 7 ) x 4 w V/然后构建 新的response回来出去
ret. d ? & @ Vurn responseBuilder.buil V . Bld();
}
private String cookieHeader(List<* % _ p ;Cookie>p X S Z C Z E J m cookiei K z _ L 0 9s) {
StringBuildei ` s d E w er cookieHeader = new StringBuilder();
for (int i = 0, size = cookies.sizL 0 % B }e(); i < size; i++) {
if (i &gv 3 e T v K n B rt; 0) {
cookieHeader.M T  -append("; ");x R ( m P 3 n
}
Cookie cookie = c| f  Mookies.get(i);
cookieHeader.append(cookie.name()).append('=').append(cookie.value());
}
return cookieH( & C 9 8 feader.toString();
}
}

首先,chain.proceed() 履行前,对 恳求添k R Q U j L J y 5加了header:”Content-Type”、”C l Xontent-Length” 或 “Transfer-Encoding”、”Host”、”Con_ z . _ Mnection”、”Accept-Encoding”、J 9 4 H J M“CoI & N i n $ @ Aokie”、”User-Agent”,即网络层真实可履行的恳求。其间,注意h ; p { v到,默许是没有cookie处理的,需求咱们在初始化OkhttpClient时装备咱们自己4 ~ & c n ( $的cookieJ2 1 ~ar

chain.proceed() 履行后,先把呼应8 . 4 Lheader中的cooe . ! }kie存入cookieJar(假如有),然后假如没有手动添` ` K } G g ) M *加恳求header “Acce7 p _ z t * , cpt-Encoding: gzip”,那么会经过 创立能主动解压的V @ V ? * e @ { OresponseBody——GzipSource,接着构建新的response回来。

看起来B? ! y M h 9 =ridgeInterceptor也是比较好了解的。

CacheInterceptor – 缓存拦截器

CacheInteC U rcepu * I Utor,缓存拦截器,提供网络恳求缓存的存取。
咱们建议一个网络恳求,假如每次都? & J ; b经过网络的发送和读取,那么效率上是有欠缺的。假如之前有相同的恳求现已履行过一次,那么是否能够把它的成果存r | V # j q g起来,然后这次恳求直接运用呢? CacheInterceptor干的便是这个事,合理运用本地缓存,有用地削0 R n R S减网络开销、削减呼应推迟。

在解析CacheInterceptor源码前,先了解下http的缓存机制:

第一次恳求:

你想要的系列:网络请求框架OkHttp3全解系列 - (三)拦截器详解1:重试重定向、桥、缓存(重点)

第2次( & E r M恳求:

你想要的系列:网络请求框架OkHttp3全解系列 - (三)拦截器详解1:重试重定向、桥、缓存(重点)

上面两张图很好的解说了http的缓存机制B : M Y y:依据 缓存是否过期过期后是否有修正 来决议 恳求是否运用缓存。详细阐明可点击了解 完全弄懂HTTP缓存v B D A }机制及原理;

CacheInterceptor的intercept办法代码如下:

  public Response intercept(Chain chain) throws IOException {
//用reques的url 从缓存中 获取呼应 作为候选(CacheSt7 _ B Drategy决议是否运用)。
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = [ g System.curr= ! i h q S ! A LentTimeMillis();
//依据 request、候选ResponsD ! H ~e 获取缓存战略。
//缓存战略 将决议是否 运用缓存:straU _ 0 7 E xtegy.networkRequest为null,不运用网络;strategy.ca[ h y E QcheResponse为null,不运用缓存。
Cache. - n f z # = + hStrategy strategy = new CacheStrategy.Factory(now, chain.request2 O ? p , c(), cacheCandidate).get();
Request( m : # Q f R F networkRequest =C b , u ! y strategy.networkRequest;
Response cacheRes$ 4 ^ iponse = strategy.cacx T  k v ~ e LheRespD # . y c A Eonse;
//依据缓存战略更新统计指标:恳求次数、网络恳求次数、运用缓存次数
if (cache != null) {
cache.e T $ ^ , 5 U R {trackReJ U X 0sponse(strategy);
}
//有缓存 但不能用,关掉
if (cacheCandidate != n1 % ! % | T N Bull && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 网络恳Q g C q J 4 ]求、缓存 都不能用,就回来504B v j ! ? F + 8
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()s  ? E C
.request(chain.request())
.protocol(Protocol.HTTP_z b x a1_1)
.cb % 4 J ]ode(504)
.message("Uns] z E , 2 I U Ratisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedRespol Z ? ? , ; {nseAtMillis(System.currentTimeMiM e x $ Jllis())
.build();
}
// 假如不必网络,cacheResponse肯定不为空了,那么就运用缓存了,完毕了。
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//到这儿,netU J ` }workRequest != null (cacheResponse或许null,也或( J & Q Q O 8 ~许!G l % [ Znull)
//networkRequest !0 b K ]  q y h= null,便是要进行网络恳求了,所以Q 6 5 ( m 2 y ( 拦截器链 就持续 往下处理了~
Response networkResp, 5 g 7 T : ( :onse = nuC  8 V a Z 8ll;
try {
netwo? x Y c l # S -rkResponse = chain.proceed(networkRequest);
} finally {
// If wH , ] c ye're crashing on I/O or otherwise, donX ` 9 [  _ 1  t't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietlya  + J ` ,(cacheCandidate.T x b q v 2body());
}
}
// 假如网络恳求回来304,表明服务端资源没有修正,那么就 结合 网络呼应和缓} i j 2存呼应,然后B v  5 ( w ~ j o更新缓存,回来,完毕。
if (cacheResponseq m . A M W R != null) {
if (networkRw N 3 W j a | 4espoQ R + Lnse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponZ . v i D B cse.headers(), networkResponse.headers()))//结合header
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())//恳求事情
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())//承受事情b ; o Z + i
.cacheResponsJ R } { le(stripBody(cacO 4 ! l G 7 x = NheResponse{ l r | 3 T X))
.networkResponse(stripBI W I nody(networkResponse))
.build();
networkRD v 8 nesponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performe( 6 E + r % t zd by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheR4 6 r x 3esponse, response);
return response;
} else {
//假如是非304,阐明服务端资源有更新,就封闭缓存body
closeQuietly(cacheResponse.body());
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse)a D | ` X 6 W W)
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
//网络呼应_ 4 a 4 8可缓F G . Z D i : P B存(恳求和呼应的 头 Cache-Control都不是'no-store')
if (HttpHeaders.has= # O m H / X p OBody(respu p I onse) && CacheStrategy.isCacheP C M 6 S V - Gable(response, networkRequest)) {
// 写入缓存
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
//OkHttp默许只会对get恳求进行缓存,因为get恳求的数据一般是比较持久的,而post一般是1 G o交互操作,没太大含义进行缓存
//不是gej p J _t恳求就移除缓存
if (HttpMethod.invalidat0 i V [ O Z -esCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}

全体思路很清晰:运用, p b J %缓存U y ! v ~ 8 T [ 1战略4 P , 6 q U v !CacheStrategy 来 决议是否运用缓存 及怎么运用。

CacheStrategy是怎么获取的,这儿) 0 f V B k Z先不看。需求知道:strategy.networkRequest为null5 / S j _ L w,不运a m F X h j用网络;strategy.cacheResponse为null,不运用缓存

那么依照这个思路,接下来便是一系列的判别:

  1. 若 networkRequest、cacheResponsR T V u u R _e 都为null,即网络恳求、缓存 都不能用,就回来504

  2. 若 networkRequest为null,cacheResponse肯定就不为null,那么便是 不必网络,运用缓存了,就完毕了

  3. 若 networkResponse不为null,不管cacheRes3 [ k I l y xponse是否null,都会去恳求网络,获取网络呼应networkResponse

  4. 接着3,若cacheResponse不为null,且networkResponse.code是304,表明服务端资源未J = 2 h ; P修正,f T l k R M I缓存是b 8 U v还有用的,那么结合 网络呼应和缓存呼应,然后更新缓存,完毕~

  5. 接着3,若cacheResponse为null 或 cacheResponse不为null但networkResponse.code不是304,那么就写3 ] d , V , b入缓存,回来呼应,完毕~

持续看,上面( [ K *判别逻辑都是基于CacheStrategy,这儿咱们就去看看它是怎么生成的,* T 4 E 4 W ) {看起来便是一行代码:

    CacheStrategy st4 V drategy = new Cach_ 2 P S :eStrategy.Factory(now, chain.request(), cacheCandidate).get();

把恳求request、候选缓存cachh a ) D ) K aeCandidate传入 工厂类Factory,然后调用get办法,那么就看下吧:

    public Factory(lonh l X K ; % x g nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse =l C D ; & + = cacheResponse;
if (cacheRe6 I } |sponse != null) {
//获取候选缓a b x V E存的恳求时刻、呼应时刻,从header中获取 过期时刻、修正时刻、资源符号等(假如有)。
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheRb p ?esponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
for$ ! 7 l ~ . (int i = 0, size = headers.sJ 8 9 -ize(); i < size; i++) {
String fieldN% _ [ame = headers.name(i);
String value = headers.value(i);
if ("Dap b O [ L J 3 P Yte".equalsIgnoreCaseJ A u M q G B(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
} else iu R f Df ("Expires".equalsIgnoreCase(fieldName)~ 9 V ` W R) {
expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastMo3 L t Gdified = HttpDate.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnorP L qeCase(fieldName)) {
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(vV K h P W P  D Palue, -1);
}
}
}
}
pubd M A a f , 0lic CacheStrategy get() {
CacheStrategy candiB w = K $ # *date = getCandidate();
if (candidate.networkRequest != null &aW 0 Q B v Rmp;& request.cacheControlg X a D {(; S v @ * b : b).onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new Cachr u W : * h } L teStrategy(null, null);
}
return candidate;
}

Fi o 8 o H r N Sact+ 8 X d ) Xory的结构办法内,获取了候选呼应的恳求时刻、呼应时刻、过期时长、修正时刻、资源符号等。

g_ Z z I ^et办法内部先调用了getCandidate()获取到缓存战略实例, 先跟进到 getCandidate办法看看吧:

    private CacheStrategy ge@ p & G AtCandidate() {
// 没有缓存:网络恳求
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// httC ] 5 = _ps,但没有握手:网络M _ l恳求
if (request.isHttps() &&ad 2 2 b =mp; cacheResponse.handshake() == null) {
return new Cac` x a O _ e h BheStrategy(request,: w - ! f + null);
}
//网络呼应 不行缓存(恳求或呼应的 头 Cache-Control 是'no-store'):网络恳求
if (!isCach: N y &eable(cai t . 1 5 w mcheResponse, req{ q ~ ( W J uuest)) {
return new CacheStrategy(request, null);
}
//恳求头的Cache-Control是no-cache 或许 恳求头有"If-Modified-Since"或"If-None-Match":网络恳求
//4 B B & W p意思便是 不运用缓存 或d u ( H ? . c / a许 恳求G % , 1 s N . 手动 添加了头部 "If-Modified-Since"或"If-None-Match"
CacheControl requestCaching = request.cw | ( W 6 r 4 lacheCo$ M 0 Y ` % U O QntrolC 4 v ) I H X ,();
if (requestCaching.noCachi 4 V v [ = be() || hasConditions(request)) {
return new CaG Y } J XcheStrategy(request, null);
}
CacheControl responsl V O p T * h Z 7eCachiq y y q ! T [ng = cacheResponse.cacheControl();
//缓存的年纪
long ageMillis = cacheResponseAge();
//缓存的有用期
long fr N D s 0 | #reshMillis = computeFreshnessLifeti{ S b E hme();
//比较恳求头里有用期,取较小值
if (requestCaching.maxAgeSeconds() !=~ { e e -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCachinE ~ , : b $ Q gg.maxAgeSecoz & X wnds()));
}
//可承受的最小 剩余有用时刻(min-fresh标示了客户端不乐意承受 剩余有用期<=# } O smin-fresh 的缓存。)
long minFreshMilliE @ S . ! /s = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds(7 1 r  +));
}
/( X g b J 0 V/可承受的o 0 M E c ; z m最大过期时刻(max-stale指令标示了客户端乐意接纳一个现已过期了的缓存,b T d E T例如 过期了 1小时 还能够用)
long maxStaleMillis = 0;
if (!respons+ u J g 1 Q _eCaching| e 8 7.mustRevalidate() && requestCaching.maxStaleSeconds(s z r `) !=v  ~ i x d ! M v -1) {
//e ~ G t 第一个判别:是否要求必须去服务器验证资源状态
// 第二个判别:获取max-s~ A 0 ` G * Etale值,假如不等^ T P ~ 4 , L g }于-1,阐明缓存过期后还能运用指定的时长
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());q e / n n ^ .
}
//假如呼应头没有要求疏忽本地缓存 且 整合后的缓存年纪 小于 整合后的过期时刻,那么缓存就能够用
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMilJ { ; 6lis) {
Response.Builder builder = cacheResponse^ , ^ t R k T.nl e g %ewBuilder();9  O R }
//没有满意O | f m ? s E E #“可承受的最小 剩余有用时刻”,加个110正告
if (ageMillis + minFresw V r W #hMillis >= freshMillis) {
buildm P m W Wer.addHeader("Warninge k 0 & & % - 2", "110 HttpURLConnectio3 [ K 1 k : ; %n "Response is stale 3 ) ~ 6 W E %"");
}
/w y 9/isFreshnessLifetZ _ o 0 & QimeHeuristic表明没有过期时刻,那么大于一天,就加个113正告
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Wax 8 E a j 0 ) c Vrning", "113 HttpURLCon( , g l M 4nection "Heuri$ 5 # v , 5stic expiration"");
}
return new CacheSt0 A M g lrategy(null, builder.build());
}
//a [ }到这儿,阐明缓存是过期的
// 然后 找 R ~ g / u I M a缓存里的Etag、lastModified、servedDate
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;G O D
} else if^  v B U v p (lastModified != null) {
conditionName = "If-Modified-Since";
cl r # y Q J * _onditionValue = lastModifiedString;
} else if (servedDate != null) {
cH L Y  1 2 , yonditionName = "If-Modified-Since";
conditionVk v ialue = servedDateString;
} else {7 h V a ; x
//都没有,就履行惯例的网络恳求
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
//假如有,就添加到网络恳求的头部。
Headers.Builder conditionalRequestHeaders = reque^ Z g G D a yst.headers().nS s Y | C *ewBui] - q 4 @lder();
Internal.instance.addLenient(conditionalRequestHe- { 0aders,J U { m k ^ u 0 conditionName, co8 T 0 9 V YnditionValue- q b !);
Request conditionalRequest = request.newBuilder()
.headers(conditionaH ] 8 m E BlRequestHeaders.build())
.build();
//conditionalRequest表明{ } e R R r W h ^ 条件网络恳求F K } U V h 2: 有缓存但过期了,去恳求网络 询问服务端,还能不能用。能用侧回来304,不能则正常履行网路恳求。
rG I D ` 6 0 : D Jeturn new CacheStrategy(conditionalRequest, cacheResponse);
}

同样是进过一些列的判别:

  1. 没有^ ( , ] @ $ 2缓存、是https但没有握手、要求不行缓存、疏忽缓存或手动装备缓存过期,都是 直接进行 网络恳求。
  2. 以上都不满意时,假如缓存没过期,那么便是用缓存(或许要添加正告)。
  3. 假如缓存过期了,但呼应头有Etag,Last-Modified,Date,就添加这些2 | 5 R [ D fheader 进行条件网络恳求
  4. 假如缓存过期了,且呼应头没有设置Etag,Last-Modified,Datd w r oe,就进行网络恳求。

接着回头看get()办法:

    public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheS1 7 f }trategy(null, null);
}
return candidate;
}

getCandidateq 8 a / i()获取的缓存战W H ) x S I略候选后,又进行了一个判别:运用网络恳求 可是 原恳求装备b | Y = ] . T了只能运用缓存,依照上面的剖析,此刻即使有缓存,也是过期的缓存,所f U % M以又new了实例,两个值都为null。

好了,到这儿okhttp的缓存机制源码就看完了。你会发现,o} o V J h K # Q Wkhttp的缓存机制 是契合 开头 httH K s b 5 9 M ~ wp的缓存机制 那两张图的,仅仅添v [ G ) _ K加很多细节判别。

另外,注意到,缓存的读写是经过 InternalCq ! 1 5ache完成的。InternalCache是在创立d n 0 u } PCacheInterceptor实例时 用client.internalCa} L + d z u 1che()作为参数传入。而InternalCache是okhttp内部运用,相似一个代理,InternalC| ` x r 7ache的实例是 类Cache的特点。Cache是咱们初始化OkHttpClienty p O |时传入的。所以假如没有传入Cache实例是没有缓% A % 3存功用的。

        OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(getExternalCacheDir(),500 * 1024 * 1024))
.b` { 2 o I j tuild();c m I m &

缓存的增修改查,Cache是经过okhttp内部的DiskLruCache实现的,原理和jakewharton的DiskLruCache是共同的,这儿就不再叙说,这不是本文重点。

总结

好了,这篇文章到这儿内容讲完了,仍是挺丰厚的。讲解了三个拦截器的工作原理:& s @ _ u eRetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor,其3 5 L & | b + u V间CacheInterceptor是恳求缓存@ 7 ! ( z ) @ q处理,是比较重要的知识点。目前为止,还没有看到真实的网络衔接和恳求呼应读写,这些内容会在下一篇讲解 — 剩余的两个拦截器 :ConnQ = A O ] qectInterceptor、CallServerInterceptor。(否则就太长了)

欢迎持续关注,感谢!

.

你的 点赞、谈论、保藏、转发,是对我的巨大鼓励!

.

欢迎关注我的公众号

你想要的系列:网络请求框架OkHttp3全解系列 - (三)拦截器详解1:重试重定向、桥、缓存(重点)