本篇咱們就開始分析okhttp中的重要組成部分--攔截器,那麼,什麼是攔截器呢,咱們看下官網的說明 Interceptors are a powerful mechanism that can monitor, rewrite, and retry calls. 翻譯過來就是,攔截器是一種強大的機制,能夠實現網絡監聽,請求以及響應重寫,請求失敗重試等功能,攔截器是不區分同步和異步的。 攔截器是能夠自定義的,自定義攔截器又可分爲應用程序攔截器和網絡攔截器,不一樣攔截器功能又有很大不一樣,不過這裏咱們今天不打算寫自定義攔截器,其實自定義攔截器的原理和okhttp內置的五種攔截器是同樣的,咱們這篇文章是分析攔截器的內部原理,因此今天咱們只講解okhttp內置的5種攔截器,有興趣的同窗能夠去okhttp官網查看自定義攔截器的功能和實現,都有很詳細的講解,官網看這裏:攔截器 下面開始咱們今天的源碼分析html
####OkHttp攔截器流程 在okhttp中,內置的5中攔截器分別是RetryAndFollowupInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、CallServerInterceptor,執行的流程是這樣的 RetryAndFollowupInterceptor->BridgeInterceptor->CacheInterceptor->ConnectInterceptor->CallServerInterceptor 咱們用代碼來證實:java
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);
}
複製代碼
還記得getResponseWithInterceptorChain() 方法嗎?在前面的文章中咱們見過了這個方法,攔截器集合中攔截器的添加的順序就是他們的執行順序,interceptors.addAll(client.interceptors());是添加自定義的應用程序攔截器,git
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
複製代碼
這個是添加非webSocket狀況下的全部自定義的網絡攔截器。其他的攔截器都是okhttp內置的攔截器,有什麼能證實攔截器的執行順序是按照添加的順序來的呢,OK,讓咱們往下看github
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
複製代碼
這個是建立真正的攔截器鏈,看到傳入的攔截器集合沒有,看到了,好的,咱們接着往下看,web
return chain.proceed(originalRequest);
複製代碼
這裏調用了攔截器鏈的proceed方法,這裏面就開始攔截器的鏈式調用了,咱們進去看看緩存
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
·····//省略部分代碼
// Call the next interceptor in the chain.
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;
}
複製代碼
能夠看到,proceed方法裏面調用了一個重載的proceed方法,咱們繼續看,能夠看到這個proceed方法中,從新生成了一個新的攔截器鏈,咱們看第五個參數是index+1,指的是從interceptors參數中的index+1索引的位置向後取攔截器,將這些攔截器從新生成一個新的攔截器,bash
Interceptor interceptor = interceptors.get(index);
複製代碼
這行代碼就是獲取當前的攔截器,服務器
Response response = interceptor.intercept(next);
複製代碼
這行代碼是調用當前攔截器的intercept方法,將新的攔截器鏈傳入,獲得咱們要的response對象,這個方法的內部,就是鏈式調用攔截器鏈中的攔截器,具體信息咱們下面逐個攔截器進行分析,首先咱們來分析攔截器鏈中的第一個攔截器RetryAndFollowUpInterceptor #####OkHttp攔截器之RetryAndFollowUpInterceptor解析 說到這個攔截器,我想你們應該不是那麼陌生,由於前面咱們已經見到過了,具體哪裏呢,咱們來看下cookie
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
複製代碼
看最後一行,就是這個攔截器的建立,原來這樣子,在建立Call對象的時候就已經建立了這個攔截器實例。這樣在建立攔截器鏈的時候就不用再建立了,經過上面的分析咱們知道,在攔截器鏈的proceed方法中,先調用攔截器鏈中的第一個攔截器的intercept方法,前面講過,第一個攔截器是RetryAndFollowUpInterceptor攔截器,那麼咱們就進入到這個攔截器的intercept方法中看看網絡
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources. if (releaseConnection) { streamAllocation.streamFailed(null); streamAllocation.release(); } } // Attach the prior response if it exists. Such responses never have a body. if (priorResponse != null) { response = response.newBuilder() .priorResponse(priorResponse.newBuilder() .body(null) .build()) .build(); } Request followUp = followUpRequest(response, streamAllocation.route()); if (followUp == null) { if (!forWebSocket) { streamAllocation.release(); } return response; } closeQuietly(response.body()); if (++followUpCount > MAX_FOLLOW_UPS) { streamAllocation.release(); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } if (followUp.body() instanceof UnrepeatableRequestBody) { streamAllocation.release(); throw new HttpRetryException("Cannot retry streamed HTTP body", response.code()); } if (!sameConnection(response, followUp.url())) { streamAllocation.release(); streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(followUp.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; } else if (streamAllocation.codec() != null) { throw new IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?"); } request = followUp; priorResponse = response; } } 複製代碼
咱們從上往下開始分析,先是獲取攔截器鏈的Request對象,而後將傳進來的攔截器鏈轉換成真正的攔截器鏈,並獲取攔截器鏈中的Call對象,這個對象是在攔截器鏈建立的時候就已經賦值進去啦,接着咱們看下streamAllocation對象,這裏將OkHttpClient的鏈接池,還有請求的url的地址信息,請求對象call,事件監聽傳遞進去,生成了一個分配的流對象,用於和服務器傳遞數據。 接着咱們看下面的while循環,首先判斷這個請求是否被取消了,若是是被取消了,就關閉流,並拋出異常。 而後在下面的try代碼塊中,咱們看到了真正的攔截器鏈又調用了proceed方法來獲取咱們想要的response對象,可見,真正獲得response對象並非在RetryAndFollowUpInterceptor攔截器中,這個攔截器只是對response對象進行包裝處理而已,接下來咱們繼續往下看,在catch代碼塊中,咱們看到捕獲了RouteException和io異常,並在某些條件下進行重試,也就是重連操做,最後在finally代碼塊中,判斷若是拋出了異常,咱們就要進行資源的釋放。 繼續往下看,若是priorResponse對象不爲null,就從新構建一個body內容爲null的response,而後傳遞給followUpRequest方法生成重定向的Request對象,可是重定向的請求的生成也是須要條件的,那麼咱們就進入到這個方法中看看
private Request followUpRequest(Response userResponse, Route route) throws IOException {
if (userResponse == null) throw new IllegalStateException();
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
case HTTP_PROXY_AUTH:
Proxy selectedProxy = route != null
? route.proxy()
: client.proxy();
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
}
return client.proxyAuthenticator().authenticate(route, userResponse);
case HTTP_UNAUTHORIZED:
return client.authenticator().authenticate(route, userResponse);
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
// "If the 307 or 308 status code is received in response to a request other than GET // or HEAD, the user agent MUST NOT automatically redirect the request"
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// fall-through
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
// Does the client allow redirects?
if (!client.followRedirects()) return null;
String location = userResponse.header("Location");
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);
// Don't follow redirects to unsupported protocols. if (url == null) return null; // If configured, don't follow redirects between SSL and non-SSL.
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
// Most redirects don't include a request body. Request.Builder requestBuilder = userResponse.request().newBuilder(); if (HttpMethod.permitsRequestBody(method)) { final boolean maintainBody = HttpMethod.redirectsWithBody(method); if (HttpMethod.redirectsToGet(method)) { requestBuilder.method("GET", null); } else { RequestBody requestBody = maintainBody ? userResponse.request().body() : null; requestBuilder.method(method, requestBody); } if (!maintainBody) { requestBuilder.removeHeader("Transfer-Encoding"); requestBuilder.removeHeader("Content-Length"); requestBuilder.removeHeader("Content-Type"); } } // When redirecting across hosts, drop all authentication headers. This // is potentially annoying to the application layer since they have no // way to retain them. if (!sameConnection(userResponse, url)) { requestBuilder.removeHeader("Authorization"); } return requestBuilder.url(url).build(); case HTTP_CLIENT_TIMEOUT: // 408's are rare in practice, but some servers like HAProxy use this response code. The
// spec says that we may repeat the request without modifications. Modern browsers also
// repeat the request (even non-idempotent ones.)
if (!client.retryOnConnectionFailure()) {
// The application layer has directed us not to retry the request.
return null;
}
if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
return null;
}
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
// We attempted to retry and got another timeout. Give up.
return null;
}
if (retryAfter(userResponse, 0) > 0) {
return null;
}
return userResponse.request();
case HTTP_UNAVAILABLE:
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
// We attempted to retry and got another timeout. Give up.
return null;
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
// specifically received an instruction to retry without delay
return userResponse.request();
}
return null;
default:
return null;
}
}
複製代碼
媽呀,這麼多代碼,看上去好恐怖啊,發抖,其實沒什麼,只要你們對HTTP狀態碼瞭解了,而後根據狀態碼來看便可,若是對狀態碼不瞭解,能夠看這裏狀態碼大全 言歸正傳,咱們繼續看咱們的代碼,咱們只須要重點關注case值爲30x的狀態碼便可,由於只有30x的纔是重定向有關的,咱們能夠看到這部分代碼從新根據傳進來的response對象信息來生成了新的Request對象,只是進行了部分請求頭的處理,其實這和咱們前面生成Request對象的性質是同樣的,只不過這裏由於是重定向,OkHttp就幫你生成了Request對象並進行了地址和請求頭的處理而已,如今咱們回過頭來看攔截器的intercept方法,
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
複製代碼
這裏判斷重定向的Request對象followUp是否爲空,若是爲空說明不須要重定向操做,就直接將前面獲得的response對象返回便可
closeQuietly(response.body());
複製代碼
若是是重定向請求,纔會執行到這裏,這裏是將以前的response對象的body進行關閉,由於其中含有流對象, 接着咱們看下面這塊代碼
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
複製代碼
這裏是根據以前請求獲得的response對象判斷和當前重定向請求的url是否同樣,若是不同,則進入if代碼塊中,將以前的流對象進行釋放,並根據重定向請求從新生成新的分配流對象。 下面進行簡單的賦值操做,再次執行while循環,也就再次執行下面一行代碼
response = realChain.proceed(request, streamAllocation, null, null);
複製代碼
分析到這裏,咱們就將RetryAndFollowUpInterceptor攔截器的intercept方法分析完成,下面咱們來分析第二個攔截器BridgeInterceptor #####OkHttp攔截器之BridgeInterceptor解析 一樣的,攔截器中最重要的方法就是intercept方法了,咱們來看看BridgeInterceptor攔截器的intercept方法
@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");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing // the transfer stream. 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(); } 複製代碼
下面咱們來從上往下分析下,首先獲得攔截器鏈中的Request請求對象,並獲得Request的構造器,而後獲取Request對象的請求體,接下來, 若是請求體不爲null,那就獲取請求體的Content-Type值,若是不爲空,就將Request構造器的header中添加Content-Type屬性,而後根據請求體的內容長度來設置Content-Length屬性和Transfer-Encoding屬性,下面的代碼直到攔截器鏈調用proceed方法以前都是設置Request對象的header屬性,下面咱們跳過這行代碼繼續往下分析,下面就是對響應體response進行處理了,判斷是不是支持gzip壓縮,若是支持gzip壓縮而且響應頭中格式爲gzip格式,就對響應體進行解壓縮處理,而後移除掉Content-Encoding屬性和Content-Length屬性,將處理過的header傳入響應體的構造器,最後生成並返回處理過的response對象。 接下來咱們繼續分析CacheInterceptor攔截器,要了解CacheInterceptor首先咱們先來了解下OkHttp的緩存策略 #####OkHttp緩存策略源碼分析 首先咱們看下okhttp中如何使用緩存
OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(new File("cache"),20*1024*1024))
.readTimeout(5, TimeUnit.SECONDS).build();
複製代碼
在建立OkHttpClient的對象的時候,只須要調用構造器的cache方法,寫入對應的參數便可,Cache構造函數中file表示緩存的目錄,後面表示緩存的大小,咱們點進Cache類中看下,看下構造函數
Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
複製代碼
在構造函數中咱們看到了DiskLruCache,就應該明白了內部原來是用的DiskLruCache來進行緩存的,對於緩存來講,最重要的就是put和get方法,那如今咱們分別來看下cache的put和get方法
@Nullable CacheRequest put(Response response) {
String requestMethod = response.request().method();
if (HttpMethod.invalidatesCache(response.request().method())) {
try {
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
if (!requestMethod.equals("GET")) {
// Don't cache non-GET responses. We're technically allowed to cache
// HEAD requests and some POST requests, but the complexity of doing
// so is high and the benefit is low.
return null;
}
if (HttpHeaders.hasVaryAll(response)) {
return null;
}
Entry entry = new Entry(response);
DiskLruCache.Editor editor = null;
try {
editor = cache.edit(key(response.request().url()));
if (editor == null) {
return null;
}
entry.writeTo(editor);
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}
複製代碼
咱們從上到下開始分析,首先獲取請求的方式,而後根據請求的方式去驗證緩存中是否存在無效緩存,若是存在,就將以前的緩存刪除,下面判斷請求方式是否爲get方式,若是不是get方式,就返回null,不進行緩存,這點很重要,緩存是隻緩存get方式的請求。緩存其餘方式的響應的話,要處理的細節較多,複雜性過高,弊大於利。 下面將response對象進行封裝,封裝成Entry實體對象,咱們看下Entry是如何對response對象進行封裝的,看下其構造函數
Entry(Response response) {
this.url = response.request().url().toString();
this.varyHeaders = HttpHeaders.varyHeaders(response);
this.requestMethod = response.request().method();
this.protocol = response.protocol();
this.code = response.code();
this.message = response.message();
this.responseHeaders = response.headers();
this.handshake = response.handshake();
this.sentRequestMillis = response.sentRequestAtMillis();
this.receivedResponseMillis = response.receivedResponseAtMillis();
}
複製代碼
從上面代碼中能夠看到,Entry只是對response的header部分進行封裝,並未對response的body部分進行處理,那麼在哪裏對body部分進行處理的呢,繼續往下看,而後根據請求的url生成key,而後獲取DiskLRUCache緩存的editor對象,Entry對象調用writeTo方法將Entry對象寫入editor進行緩存,進入到writeTo方法中
public void writeTo(DiskLruCache.Editor editor) throws IOException {
BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));
sink.writeUtf8(url)
.writeByte('\n');
sink.writeUtf8(requestMethod)
.writeByte('\n');
sink.writeDecimalLong(varyHeaders.size())
.writeByte('\n');
for (int i = 0, size = varyHeaders.size(); i < size; i++) {
sink.writeUtf8(varyHeaders.name(i))
.writeUtf8(": ")
.writeUtf8(varyHeaders.value(i))
.writeByte('\n');
}
sink.writeUtf8(new StatusLine(protocol, code, message).toString())
.writeByte('\n');
sink.writeDecimalLong(responseHeaders.size() + 2)
.writeByte('\n');
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
sink.writeUtf8(responseHeaders.name(i))
.writeUtf8(": ")
.writeUtf8(responseHeaders.value(i))
.writeByte('\n');
}
sink.writeUtf8(SENT_MILLIS)
.writeUtf8(": ")
.writeDecimalLong(sentRequestMillis)
.writeByte('\n');
sink.writeUtf8(RECEIVED_MILLIS)
.writeUtf8(": ")
.writeDecimalLong(receivedResponseMillis)
.writeByte('\n');
if (isHttps()) {
sink.writeByte('\n');
sink.writeUtf8(handshake.cipherSuite().javaName())
.writeByte('\n');
writeCertList(sink, handshake.peerCertificates());
writeCertList(sink, handshake.localCertificates());
sink.writeUtf8(handshake.tlsVersion().javaName()).writeByte('\n');
}
sink.close();
}
複製代碼
能夠看到,writeTo方法並未對body部分進行緩存,那麼只剩下最後一個return語句了,是在return中進行緩存的嗎???咱們點進去看一下,
private final class CacheRequestImpl implements CacheRequest {
private final DiskLruCache.Editor editor;
private Sink cacheOut;
private Sink body;
boolean done;
CacheRequestImpl(final DiskLruCache.Editor editor) {
this.editor = editor;
this.cacheOut = editor.newSink(ENTRY_BODY);
this.body = new ForwardingSink(cacheOut) {
@Override public void close() throws IOException {
synchronized (Cache.this) {
if (done) {
return;
}
done = true;
writeSuccessCount++;
}
super.close();
editor.commit();
}
};
}
·····
@Override public Sink body() {
return body;
}
}
複製代碼
能夠看到,裏面有個body變量,這個body就是咱們的response的響應體,咱們的response響應體就是在這個CacheRequestImpl裏面進行緩存操做的,咱們還看到了CacheRequestImpl是實現了CacheRequest接口,爲何要實現這個接口呢,實際上是爲了給咱們後面要講的CacheInterceptor攔截器使用的,講完了put方法咱們再來看下Cache的get方法
@Nullable Response get(Request request) {
String key = key(request.url());
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
try {
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
Response response = entry.response(snapshot);
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
return response;
}
複製代碼
在get方法的上面咱們能夠看到,首先經過調用key方法經過請求的url地址獲取緩存的key值,而後經過DiskLRUCache對象獲取一個緩存的快照snapshot,接下來,將snapshot中的數據封裝成Entry實體對象,而後調用Entry對象的response獲得咱們要的response對象,咱們看下Entry的response方法是這麼實現的
public Response response(DiskLruCache.Snapshot snapshot) {
String contentType = responseHeaders.get("Content-Type");
String contentLength = responseHeaders.get("Content-Length");
Request cacheRequest = new Request.Builder()
.url(url)
.method(requestMethod, null)
.headers(varyHeaders)
.build();
return new Response.Builder()
.request(cacheRequest)
.protocol(protocol)
.code(code)
.message(message)
.headers(responseHeaders)
.body(new CacheResponseBody(snapshot, contentType, contentLength))
.handshake(handshake)
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(receivedResponseMillis)
.build();
}
複製代碼
從上面代碼中咱們能夠看到,首先經過構造器將Request對象構造出來,而後調用Response類的構造器將response對象構造出來。回過頭來咱們再看下get方法,而後將request對象和獲得的response對象進行匹配,看看是否成立,若是不成立就返回null。 最後將從緩存中獲得的response對象返回出來。 #####OkHttp攔截器之CacheInterceptor解析 接着咱們分析CacheInterceptor攔截器的intercept方法
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } // 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.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 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(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } 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. } } } return response; } 複製代碼
媽呀,這麼長,簡直要懵逼,好怕怕,····,但是怕仍是要看,咱們開始從頭開始分析, 首先根據攔截器鏈中的request對象從緩存中嘗試獲取緩存的response對象,接着往下看,CacheStrategy,???這是什麼鬼,無奈英文不行,查下詞典,CacheStrategy:緩存策略,好吧,猜測應該就是關於如何處理緩存的類,咱們打開看看,上來先看到這部分代碼
/** The request to send on the network, or null if this call doesn't use the network. */ public final @Nullable Request networkRequest; /** The cached response to return or validate; or null if this call doesn't use a cache. */
public final @Nullable Response cacheResponse;
CacheStrategy(Request networkRequest, Response cacheResponse) {
this.networkRequest = networkRequest;
this.cacheResponse = cacheResponse;
}
複製代碼
看到兩個變量networkRequest、cacheResponse,看看變量的註釋,好像是說一個是使用網絡時的request對象,一個是緩存的response對象。好像明白了,應該是指的是具體是經過網絡獲取仍是從緩存中獲取咱們想要的response對象,若是是從網絡中獲取就是使用networkRequest,若是是從緩存中獲取,就是使用cacheResponse。好的,大概明白了這個緩存策略類的做用,回過頭來咱們再來看intercept方法,咱們看下這個緩存策略是如何獲得的,進入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 CacheStrategy(null, null); } return candidate; } 複製代碼
只有簡單幾行代碼,看得出來核心就是第一行代碼,咱們進入到getCandidate方法中看下實現,
/** Returns a strategy to use assuming the request can use the network. */
private CacheStrategy getCandidate() {
// No cached response.
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// Drop the cached response if it's missing a required handshake. if (request.isHttps() && cacheResponse.handshake() == null) { return new CacheStrategy(request, null); } // If this response shouldn't have been stored, it should never be used
// as a response source. This check should be redundant as long as the
// persistence store is well-behaved and the rules are constant.
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.immutable()) {
return new CacheStrategy(null, cacheResponse);
}
long ageMillis = cacheResponseAge();
long freshMillis = computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
long maxStaleMillis = 0;
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
複製代碼
又是長長的一大段,不怕,來咱們慢慢看看這個方法中緩存策略的生成過程,首先來看
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
複製代碼
若是沒有緩存response對象存在,就建立一個請求網絡的緩存策略,OK,繼續
// Drop the cached response if it's missing a required handshake. if (request.isHttps() && cacheResponse.handshake() == null) { return new CacheStrategy(request, null); } 複製代碼
若是是HTTPS請求,而且緩存response對象的握手數據爲null,就建立一個請求網絡的緩存策略返回,繼續
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
複製代碼
若是是不可緩存的,就建立一個請求網絡的緩存策略返回,let's繼續
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
複製代碼
判斷請求中的緩存控制信息是否容許緩存或者查看是否知足指定條件,若是知足了指定的條件,就建立請求網絡的緩存策略並返回,go on
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.immutable()) {
return new CacheStrategy(null, cacheResponse);
}
複製代碼
若是響應的response中的緩存控制值是不可變的,說明數據穩定,建立一個使用緩存的緩存策略返回便可,繼續 下面的代碼不是很明白,不過不影響以前代碼的理解,先跳過,回頭有時間再細細研究這段代碼。 回到CacheInterceptor的intercept方法中來,獲得了緩存策略對象strategy,而後下面代碼
if (cache != null) {
cache.trackResponse(strategy);
}
複製代碼
這步操做是啥操做呢,咱們看下實現
synchronized void trackResponse(CacheStrategy cacheStrategy) {
requestCount++;
if (cacheStrategy.networkRequest != null) {
// If this is a conditional request, we'll increment hitCount if/when it hits. networkCount++; } else if (cacheStrategy.cacheResponse != null) { // This response uses the cache and not the network. That's a cache hit.
hitCount++;
}
}
複製代碼
原來是請求次數計算和緩存命中次數的計算,繼續往下看
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } 複製代碼
若是從緩存中獲得的數據不爲null,而且通過緩存策略的計算,緩存策略中的response對象爲null,就說明這個緩存不可用,就將其關閉。now go on
// 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(); } 複製代碼
若是不能使用網絡請求也不能使用緩存,那麼久返回一個504的錯誤。
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
複製代碼
若是不能使用網絡,咱們就將緩存策略獲得的response進行封裝並返回。 下面就是從網絡中獲取數據了,調用了攔截器鏈的proceed方法,這個咱們先跳過,繼續往下看
// 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(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } 複製代碼
若是從網絡中獲得的response中的狀態碼爲304,那麼就將從緩存中獲取的response進行封裝並返回。 最後
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.
}
}
}
複製代碼
首先判斷響應是否有響應體,而且是不是可緩存的,若是可緩存,就進行緩存,而後對請求的方法進行判斷,若是是無效的,就將數據中緩存中移除。 分析到這裏,就將緩存攔截器分析完成了,下面咱們繼續看鏈接攔截器。 #####OkHttp攔截器之ConnectInterceptor解析 按照慣例,咱們依舊是要看攔截器的intercept方法的實現,
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
複製代碼
驚了個呆,這個intercept方法這麼短嗎?是的,就是這麼短,可是,短不見得就是簡單,嗯哼,咱們來分析下這個方法中的代碼。 首先獲取真正的攔截器鏈,而後經過攔截器鏈獲取request請求對象,而後獲取攔截器中的StreamAllocation對象,咱們知道,這個對象是在前面就已經建立好了的,前面已經分析過,這裏就再也不講啦,好的,繼續,咱們看到streamAllocation對象建立了一個叫作HttpCodec的對象實例,這個是什麼東東呢,其實就是對咱們的request對象編碼和對response對象進行解碼操做的類,而後streamAllocation建立了一個RealConnection對象,而後將這些新建立的對象傳入到了下一個攔截器中。
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
複製代碼
咱們來看關鍵代碼,鏈接操做是在try代碼塊中,是findHealthyConnection方法,繼續跟進,
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again. if (!candidate.isHealthy(doExtensiveHealthChecks)) { noNewStreams(); continue; } return candidate; } } 複製代碼
看關鍵代碼,就是while循環的第一行代碼,仍是要跟進,城市套路深,我要回農村····
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
Connection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
···//忽略部分代碼
// Attempt to use an already-allocated connection. We need to be careful here because our
// already-allocated connection may have been restricted from creating new streams.
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();
if (this.connection != null) {//先找是否有可用且健康的鏈接,若是有,咱們就進行復用
// We had an already-allocated connection and it's good. result = this.connection; releasedConnection = null; } if (!reportedAcquired) { // If the connection was never reported acquired, don't report it as released!
releasedConnection = null;
}
if (result == null) {//這裏說明沒有可用的鏈接,那麼咱們就要從鏈接池中獲取了
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);//從鏈接池中獲取
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
····//忽略部分代碼
//在這裏進行真正的鏈接操做
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// Pool the connection.
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
複製代碼
從上面的代碼塊的註釋中能夠看到,真正的鏈接是執行connect方法,咱們再進一層看看connect是如何實現的
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
EventListener eventListener) {
//判斷是否已經鏈接
if (protocol != null) throw new IllegalStateException("already connected");
RouteException routeException = null;
List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
····//忽略部分代碼
while (true) {
try {//關鍵代碼在這裏,判斷是否須要進行隧道鏈接,若是須要就進行隧道鏈接
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
if (rawSocket == null) {
// We were unable to connect the tunnel but properly closed down our resources.
break;
}
} else {
//這裏進行socket鏈接
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
break;
} catch (IOException e) {
····//忽略部分代碼
}
}
····//忽略部分代碼
}
複製代碼
好了,看完這個方法,咱們就知道具體是在哪裏進行鏈接操做的了,到此爲止呢,咱們也算是分析完了ConnectInterceptor攔截器
#####OkHttp攔截器之CallServerInterceptor解析 國際慣例,咱們先貼出intercept方法的代碼
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
realChain.eventListener().requestHeadersStart(realChain.call());
httpCodec.writeRequestHeaders(request);//在這裏經過httpcodec向HTTP的流裏寫入request的header信息
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100 // Continue" response before transmitting the request body. If we don't get that, return
// what we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
//這裏咱們進行寫入請求體的操做
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection // from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
streamAllocation.noNewStreams();
}
}
//到這裏,咱們就完成了request的請求工做
httpCodec.finishRequest();
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (code == 100) {
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
responseBuilder = httpCodec.readResponseHeaders(false);
response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
//這裏咱們獲得了咱們須要的response對象
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
複製代碼
好了,CallServerInterceptor攔截器的分析到此結束, 整個okhttp的源碼分析也結束了,以上只是我的的閱讀源碼的理解,之後還會常常閱讀的,逐漸提高逐漸閱讀源碼的能力,若是有問題歡迎你們的指正。