引
年后就是跳槽的高峰期者吁,年前參加了幾場面試柳骄,基本上都會問對于常用的第三方框架的了解病梢,由于之前主要從事系統(tǒng)開發(fā)的工作,所以對于常用的網(wǎng)絡框架不是很了解辰晕。借此機會學習總結(jié)一下OkHttp網(wǎng)絡請求框架蛤迎。
本文從官方提供的示例入手,嘗試分析學習OkHttp框架(3.9.1版本)的源碼含友,
OkHttp簡介
An HTTP & HTTP/2 client for Android and Java applications
根據(jù)官網(wǎng)介紹替裆,OkHttp是一個用于java和Android的HTTP&HTTP/2請求的客戶端。
而在Android系統(tǒng)的原生庫類中窘问,有兩個用于http請求的庫類辆童,在Android2.2之前,推薦使用HttpClient惠赫,而在Android2.2之后推薦使用HttpURLConnection把鉴。
優(yōu)點
相比于官方的原生網(wǎng)絡請求庫,OkHttp有以下優(yōu)點:
- 支持HTTP/2, HTTP/2通過使用多路復用技術(shù)在一個單獨的TCP連接上支持并發(fā),通過在一個連接上一次性發(fā)送多個請求來發(fā)送或接收數(shù)據(jù)
- 通過連接池復用技術(shù)(Http)減少延時
- 支持Gzip降低下載大小
- 支持響應緩存避免了重復請求的網(wǎng)絡
get流程
官方示例
//1.創(chuàng)建OkHttpClient
OkHttpClient client = new OkHttpClient();
//2.創(chuàng)建Request儿咱,并填入url信息
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
//3.通過OkHttpClient的newcall進行同步請求
Response response = client.newCall(request).execute();
//4.返回請求結(jié)果
return response.body().string();
}
流程分析
1.創(chuàng)建OkHttpClient
構(gòu)建了一個OkHttpClient
//1.創(chuàng)建OkHttpClient
OkHttpClient client = new OkHttpClient();
public OkHttpClient() {
//通過默認builder構(gòu)建一個新的OkHttpClient
this(new Builder());
}
//初始化builder用于配置各種參數(shù)
public Builder() {
//異步請求的執(zhí)行策略調(diào)度器
dispatcher = new Dispatcher();
//默認的協(xié)議列表
protocols = DEFAULT_PROTOCOLS;
//默認的連接規(guī)范
connectionSpecs = DEFAULT_CONNECTION_SPECS;
//指標事件的監(jiān)聽器
eventListenerFactory = EventListener.factory(EventListener.NONE);
//默認的代理選擇器
proxySelector = ProxySelector.getDefault();
//默認不管理cookie
cookieJar = CookieJar.NO_COOKIES;
//默認的socket工廠
socketFactory = SocketFactory.getDefault();
//默認的主機名驗證
hostnameVerifier = OkHostnameVerifier.INSTANCE;
//固定證書后雷,默認不開啟
certificatePinner = CertificatePinner.DEFAULT;
//響應服務器身份驗證質(zhì)詢苞慢,默認不進行響應
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
//初始化連接池
connectionPool = new ConnectionPool();
//默認DNS
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
//超時時間
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
2.創(chuàng)建Request,并填入url信息
通過構(gòu)造器模式創(chuàng)建Request對象
//2.創(chuàng)建Request,并填入url信息
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
首先通過Builder()方法構(gòu)建一個Builder對象
//Builder
public Builder() {
//默認方法為get
this.method = "GET";
//初始化一個空的請求頭
this.headers = new Headers.Builder();
}
然后通過url(url)方法對url進行了設(shè)置
public Builder url(String url) {
//檢測傳入的url是否為空
if (url == null) throw new NullPointerException("url == null");
//將socket url替換為Http url
if (url.regionMatches(true, 0, "ws:", 0, 3)) {
url = "http:" + url.substring(3);
} else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
url = "https:" + url.substring(4);
}
//根據(jù)傳入的url生成一個HttpUrl
HttpUrl parsed = HttpUrl.parse(url);
//如果為空則說明傳入的不是一個格式正確的http/https url
if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
//將http傳入url,返回Builder
return url(parsed);
}
//將傳入的HttpUrl賦值為成員變量后返回Builder
public Builder url(HttpUrl url) {
if (url == null) throw new NullPointerException("url == null");
this.url = url;
return this;
}
最后調(diào)用build()方法完成Request的創(chuàng)建
public Request build() {
//檢測url是否為空置森,若為空則拋出異常
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
//Request
Request(Builder builder) {
//請求地址
this.url = builder.url;
//請求方法
this.method = builder.method;
//請求頭
this.headers = builder.headers.build();
//請求體
this.body = builder.body;
//tag標記愕难,可用來統(tǒng)一刪除
this.tag = builder.tag != null ? builder.tag : this;
}
可見url是創(chuàng)建Request時不可缺少的一個部分丰捷,一個Request中必須填入其url
而Request中包換五個部分篡石,除tag外分別與Http請求中的請求地址、請求方法使套、請求頭和請求體四部分別對應罐呼。至于Http請求所需的請求協(xié)議,Okhttp是通過使用請求協(xié)議的協(xié)商升級來進行確定的侦高。
3.通過OkHttpClient的newcall進行同步請求
第三步是整個網(wǎng)絡請求中的重中之重嫉柴,它通過對我們的Request進行解析生成相應的call來獲取我們所需的Response。
Response response = client.newCall(request).execute();
首先通過newCall(request)方法根據(jù)請求創(chuàng)建了一個call
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
//傳入?yún)?shù)生成Realcall
RealCall call = new RealCall(client, originalRequest, forWebSocket);
//為生成call創(chuàng)建一個eventListener實例奉呛,用于監(jiān)聽請求的各個階段
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
//三個參數(shù)分別對應之前創(chuàng)建的OkHttpClient计螺,傳入的Request,已經(jīng)是否為WebSocket此時為false
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
//將傳入的參數(shù)賦值給對應的變量
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
//生成一個RetryAndFollowUpInterceptor,用于失敗重試以及重定向
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
然后通過execute()進行同步請求
@Override public Response execute() throws IOException {
synchronized (this) {
//如果該請求正在運行拋出異常瞧壮,否則將運行標志位置為true登馒,防止重復請求
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//捕獲調(diào)用堆棧的跟蹤
captureCallStackTrace();
//告知eventlisten請求開始
eventListener.callStart(this);
try {
//通過dispatcher的executed來實際執(zhí)行
client.dispatcher().executed(this);
//經(jīng)過一系列"攔截"操作后獲取結(jié)果
Response result = getResponseWithInterceptorChain();
//如果result為空拋出異常
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
//告知eventlisten請求失敗
eventListener.callFailed(this, e);
throw e;
} finally {
//通知dispatcher執(zhí)行完畢
client.dispatcher().finished(this);
}
}
在這一步中 client.dispatcher().executed(this) 僅僅是將call加入一個隊列,并沒有真正開始進行網(wǎng)絡請求
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
真正開始進行網(wǎng)絡請求的方法是getResponseWithInterceptorChain()咆槽,這也是此次網(wǎng)絡請求中最為重要的一個方法
Response getResponseWithInterceptorChain() throws IOException {
//創(chuàng)建一個攔截器數(shù)組用于存放各種攔截器
List<Interceptor> interceptors = new ArrayList<>();
//向數(shù)組中添加用戶自定義的攔截器
interceptors.addAll(client.interceptors());
//1.向數(shù)組中添加retryAndFollowUpInterceptor用于失敗重試和重定向
interceptors.add(retryAndFollowUpInterceptor);
//2.向數(shù)組中添加BridgeInterceptor用于把用戶構(gòu)造的請求轉(zhuǎn)換為發(fā)送給服務器的請求陈轿,把服務器返回的響應轉(zhuǎn)換為對用戶友好的響應。
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//3.向數(shù)組中添加CacheInterceptor用于讀取緩存以及更新緩存
interceptors.add(new CacheInterceptor(client.internalCache()));
//4.向數(shù)組中添加ConnectInterceptor用于與服務器建立連接
interceptors.add(new ConnectInterceptor(client));
//如果不是webSocket添加networkInterceptors
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
//5.向數(shù)組中添加CallServerInterceptor用于從服務器讀取響應的數(shù)據(jù)
interceptors.add(new CallServerInterceptor(forWebSocket));
//根據(jù)上述的攔截器數(shù)組生成一個攔截鏈
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
//通過攔截鏈的proceed方法開始整個攔截鏈事件的傳遞
return chain.proceed(originalRequest);
}
在getResponseWithInterceptorChain()方法中我們可以發(fā)現(xiàn)有許多不同功能的攔截器,主要列舉一下默認已經(jīng)實現(xiàn)的幾個攔截器的作用:
- retryAndFollowUpInterceptor 負責失敗重試和重定向
- BridgeInterceptor 負責把用戶構(gòu)造的請求轉(zhuǎn)換為發(fā)送給服務器的請求麦射,把服務器返回的響應轉(zhuǎn)換為對用戶友好的響應蛾娶。
- CacheInterceptor 負責讀取緩存以及更新緩存
- ConnectInterceptor 負責建立連接
- CallServerInterceptor 負責發(fā)送和讀取數(shù)據(jù)
而這些攔截器的具體實現(xiàn)我們后續(xù)在看,按照流程在這個方法中通過new RealInterceptorChain()生成了一個攔截鏈潜秋,然后通過它proceed()方法開始運行這條攔截鏈
//RealInterceptorChain的構(gòu)造函數(shù)主要是將傳入的參數(shù)用變量記錄下來蛔琅,其中的index參數(shù)用來記錄當前的攔截器
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
//調(diào)用proceed傳入request
@Override public Response proceed(Request request) throws IOException {
//在本例中streamAllocation、httpCodec半等、connection均為null
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
//當index大于攔截器數(shù)組的大小時拋出異常
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use 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");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// 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);
//調(diào)用當前攔截器的intercept(),并傳入下一個攔截器的攔截鏈
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
每個攔截器的intercept()方法各不相同呐萨,下面按照前文的添加順序具體分析其實現(xiàn)與功能
1.RetryAndFollowUpInterceptor
@Override public Response intercept(Chain chain) throws IOException {
//從傳入攔截鏈中獲取request杀饵、call、eventListener
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
//創(chuàng)建一個StreamAllocation谬擦,傳遞給后面的攔截鏈切距,用于管理Connections、Streams惨远、Calls三者之間的關(guān)系
streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
call, eventListener, callStackTrace);
//記錄重試次數(shù)
int followUpCount = 0;
Response priorResponse = null;
//開啟循環(huán)
while (true) {
//判斷是否取消谜悟,如果取消則通過streamAllocation釋放連接并拋出IOException
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
//調(diào)用傳入的攔截鏈的proceed方法,執(zhí)行下一個攔截器北秽,捕獲拋出的異常并進行處理
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(), false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
//捕獲到IO異常贺氓,判斷是否要恢復蔚叨,否的話拋出異常
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
//根據(jù)標志位releaseConnection判斷是否需要釋放連接
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();
}
//根據(jù)response來生成一個Request對象用于重定向和重連
Request followUp = followUpRequest(response);
//如果followUp為空,則說明無須重連或重定向辙培,直接釋放連接返回response
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
//調(diào)用ResponseBody的close方法蔑水,關(guān)閉stream和相關(guān)資源
closeQuietly(response.body());
//重連次數(shù)高于限定次數(shù)(20)直接釋放連接拋出異常
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//Request的請求體屬于不可重復提交的請求體則關(guān)閉連接拋出異常
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);
} 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;
}
}
上述代碼即為RetryAndFollowUpInterceptor intercept的實現(xiàn)流程,它主要負責失敗重試和重定向扬蕊。簡化流程如下:
- 實例化StreamAllocation傳入到接下來的攔截鏈中
- 開啟循環(huán)搀别,執(zhí)行下一個調(diào)用鏈(攔截器),等待響應(Response)
- 如果等待響應(Response)的過程中拋出異常尾抑,根據(jù)異常決定是否進行重連或重定向歇父,否:退出
- 根據(jù)響應生成的followUp決定是否進行重連或重定向,否:返回響應(Response)
- 關(guān)閉響應結(jié)果
- 判斷重連數(shù)是否達到最大值再愈,是:釋放連接庶骄、退出
- 判斷followUp的請求體是否能重復提交,否:釋放連接践磅、退出
- 檢測是否為相同連接单刁,否:重新實例化StreamAllocation
- 循環(huán)以上步驟
2.BridgeInterceptor
根據(jù)攔截鏈的proceed方法可知,會調(diào)用到BridgeInterceptor的intercept()方法
@Override public Response intercept(Chain chain) throws IOException {
//從傳入攔截鏈中獲取request以及requestBuilder
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
//獲取request的請求體,若不為空則添加部分請求頭信息
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
//添加contentType
requestBuilder.header("Content-Type", contentType.toString());
}
//根據(jù)contentLength確定添加Content-Length還是Transfer-Encoding
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");
}
}
//若無自定義host,添加默認host
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
//若無自定義Connection,添加默認Connection(Keep-Alive)
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");
}
//如果在創(chuàng)建OKHttpClient時創(chuàng)建的cookieJar不為NO_COOKIE羔飞,且cookie不為空則添加Cookie
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
//若User-Agent為空肺樟,則添加默認User-Agent,默認為OkHttp版本號逻淌,該例為okhttp/3.9.1
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
//初始化添加了頭信息的request并傳入下一個攔截鏈中
Response networkResponse = chain.proceed(requestBuilder.build());
//請求完成后么伯,根據(jù)返回的response存儲cookies(如果需要,否則該方法不作任何操作)
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//判斷服務器是否支持gzip壓縮格式,如果支持則交給kio壓縮
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)));
}
//返回處理后的response
return responseBuilder.build();
}
上述代碼就是BridgeInterceptor intercept的實現(xiàn)流程卡儒,它主要用于負責把用戶構(gòu)造的請求轉(zhuǎn)換為發(fā)送給服務器的請求田柔,把服務器返回的響應轉(zhuǎn)換為對用戶友好的響應。簡化流程如下:
- 根據(jù)request信息骨望,為請求添加頭信息
- 將封裝好的request傳入下一個攔截鏈,并返回Response
- 根據(jù)返回的response進行cookie硬爆、Gzip處理
- 返回處理好的Gzip
3.CacheInterceptor
@Override public Response intercept(Chain chain) throws IOException {
//讀取配置中的候選緩存,讀取序列依次為OkHttpClient中的cache擎鸠、internalCache和null缀磕。
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//根據(jù)cacheCandidate創(chuàng)建緩存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
//緩存監(jiān)測
if (cache != null) {
cache.trackResponse(strategy);
}
//若未找到合適的緩存關(guān)閉stream和相關(guān)資源
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
//若根據(jù)緩存策略,不適用網(wǎng)絡請求即networkRequest為null劣光,且無相應緩存袜蚕,即cacheResponse為null,直接報錯返回504
// 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();
}
//若不使用網(wǎng)絡绢涡,緩存有效牲剃。直接返回緩存的Response
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//若需要使用網(wǎng)絡則通過攔截鏈啟動下一個攔截器發(fā)起網(wǎng)絡請求
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) {
//網(wǎng)絡請求返回304,即緩存數(shù)據(jù)未過期雄可,根據(jù)本地緩存響應和網(wǎng)絡請求響應生成Response
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 {
//否則緩存數(shù)據(jù)過期颠黎,關(guān)閉緩存。
closeQuietly(cacheResponse.body());
}
}
//根據(jù)網(wǎng)絡請求返回的數(shù)據(jù)生成response
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//如果需要OKHttpClient需要使用緩存
if (cache != null) {
//如果response存在body且允許緩存滞项,則進行本地化緩存
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;
}
上述代碼即為CacheInterceptor的intercept()方法的運行流程,主要負責讀取緩存以及更新緩存文判,簡化流程如下:
- 讀取OkhttpClient配置緩存过椎,可能為null
- 生成相應的緩存策略
- 根據(jù)緩存策略如果不使用網(wǎng)絡且無相應緩存,則直接返回504
- 根據(jù)緩存策略如果不使用網(wǎng)絡但相應緩存戏仓,則直接返回緩存響應
- 根據(jù)緩存策略如果使用網(wǎng)絡疚宇,則通過攔截鏈啟動下一個攔截器發(fā)起網(wǎng)絡請求
- 根據(jù)網(wǎng)絡響應,確定緩存是否過期赏殃,若未過期(返回304)則返回緩存
- 如果緩存過期敷待,關(guān)閉緩存并生成網(wǎng)絡請求的response
- 根據(jù)緩存要求進行本地緩存
- 返回網(wǎng)絡請求的response
4.ConnectInterceptor 負責建立連接
@Override public Response intercept(Chain chain) throws IOException {
//從傳入的攔截鏈中獲取request和streamAllocation(RetryAndFollowUpInterceptor中初始化傳入)
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,在newStream方法中會通過findHealthyConnection()方法依次嘗試從當前連接仁热、連接池榜揖、其他線路的連接池、新建連接的順序中獲取到RealConnection,然后通過RealConnection的newCodec方法分別根據(jù)Http2举哟、Http協(xié)議生成httpCodec
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//獲取RealConnection
RealConnection connection = streamAllocation.connection();
Connection
//通過攔截鏈啟動下一個攔截器,并將httpCodec和connection傳入
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
上述代碼即為ConnectInterceptor的intercept()方法的運行流程思劳,負責連接的建立,簡化流程如下:
- 讀取OkhttpClient配置request和streamAllocation
- 初始化HttpCodec
- 獲取RealConnection
- 通過攔截鏈啟動下一個攔截器,并將2妨猩、3步的對象傳入
5.CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOException {
// 通過攔截鏈獲取在ConnectInterceptor中完成初始化的HttpCodec和RealConnection潜叛,以及streamAllocation和request
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();
//通知eventListener
realChain.eventListener().requestHeadersStart(realChain.call());
//寫請求頭
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
//若請求方法允許傳輸請求體,且request的請求體不為空
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
//如果在請求頭中存在"Expect:100-continue"壶硅,說明該請求需要等待服務器回復是否能夠處理請求體威兜,服務器若不接受請求體則會返回一個非空的編碼
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
//接收服務器的返回請求,服務器若不接受請求體則會返回一個非空的響應
responseBuilder = httpCodec.readResponseHeaders(true);
}
//若responseBuilder為null庐椒,則Expect不為100-continue或服務器接收請求體,開始寫入請求體
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()) {
// 如果服務器拒絕接收請求體,且不是http2椒舵,則禁止此連接被重新使用
streamAllocation.noNewStreams();
}
}
//完成請求寫入
httpCodec.finishRequest();
//通過httpCodec獲取響應頭
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
//通過responseBuilder填入信息創(chuàng)建Response
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);
//獲取返回碼
int code = response.code();
//如果是101(升級到Http2協(xié)議),則返回一個EMPTY_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.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
//若請求或者服務器要求斷開連接扼睬,則斷開
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
//若返回204/205(服務器均未返回響應體)且響應體長度大于)則拋出異常
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的intercept()方法的運行流程逮栅,負責發(fā)送請求數(shù)據(jù)和讀取響應數(shù)據(jù)悴势,簡化流程如下:
- 讀取HttpCodec窗宇、RealConnection等對象
- 寫入請求頭
- 若需要(由請求方法和服務器決定),寫入請求體
- 讀取響應頭信息
- 若請求或響應要求斷開連接特纤,則斷開連接
- 根據(jù)響應碼讀取響應體
- 處理204/205的異常情況
- 返回響應
至此默認的五個攔截器的實現(xiàn)和功能都已經(jīng)分析完了军俊,但由于篇幅有限,所以其中有些對象并沒有深入分析捧存,如streamAllocation粪躬、HttpCodec等
4.獲取Response的響應結(jié)果
//4.返回請求結(jié)果
return response.body().string();
此時的response就是第三步中通過newCall獲取到的response
public @Nullable ResponseBody body() {
return body;
}
//以Content-Type標頭的字符集解碼的字符串形式返回響應,若未標明則用UTF-8
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
} finally {
Util.closeQuietly(source);
}
}
結(jié)語
本篇分析了官方示例中g(shù)et操作的流程昔穴,最大的特點則在于通過攔截器鏈來實現(xiàn)責任鏈鏈镰官,從而完成整個網(wǎng)絡請求的流程。
本篇文章是個人學習的總結(jié)吗货,本人能力有限泳唠,如果有錯誤歡迎斧正,謝謝宙搬。