從使用方法出發(fā),首先是怎么使用,其次是我們使用的功能在內(nèi)部是如何實現(xiàn)的,實現(xiàn)方案上有什么技巧,有什么范式。全文基本上是對 OkHttp 源碼的一個分析與導(dǎo)讀,非常建議大家下載 OkHttp 源碼之后,跟著本文,過一遍源碼。對于技巧和范式,由于目前我的功力還不到位,分析內(nèi)容沒多少,歡迎大家和我一起討論。
成都做網(wǎng)站、成都網(wǎng)站制作、成都外貿(mào)網(wǎng)站建設(shè)的開發(fā),更需要了解用戶,從用戶角度來建設(shè)網(wǎng)站,獲得較好的用戶體驗。成都創(chuàng)新互聯(lián)多年互聯(lián)網(wǎng)經(jīng)驗,見的多,溝通容易、能幫助客戶提出的運營建議。作為成都一家網(wǎng)絡(luò)公司,打造的就是網(wǎng)站建設(shè)產(chǎn)品直銷的概念。選擇成都創(chuàng)新互聯(lián),不只是建站,我們把建站作為產(chǎn)品,不斷的更新、完善,讓每位來訪用戶感受到浩方產(chǎn)品的價值服務(wù)。
首先放一張完整流程圖(看不懂沒關(guān)系,慢慢往后看):
來自O(shè)kHttp 官方網(wǎng)站。
OkHttpClient client = new OkHttpClient();
咦,怎么不見 builder?莫急,且看其構(gòu)造函數(shù):
public OkHttpClient() {
this(new Builder());
}
原來是方便我們使用,提供了一個“快捷操作”,全部使用了默認的配置。OkHttpClient.Builder
類成員很多,后面我們再慢慢分析,這里先暫時略過:
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
DNS = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
}
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
OkHttpClient
實現(xiàn)了Call.Factory
,負責根據(jù)請求創(chuàng)建新的Call
。
那我們現(xiàn)在就來看看它是如何創(chuàng)建 Call 的:
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return new RealCall(this, request);
}
如此看來功勞全在RealCall
類了,下面我們一邊分析同步網(wǎng)絡(luò)請求的過程,一邊了解RealCall
的具體內(nèi)容。
我們首先看RealCall#execute
:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed"); // (1)
executed = true;
}
try {
client.dispatcher().executed(this); // (2)
Response result = getResponseWithInterceptorChain(); // (3)
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this); // (4)
}
}
這里我們做了 4 件事:
call#clone
方法進行克隆。client.dispatcher().executed(this)
來進行實際執(zhí)行dispatcher
是剛才看到的OkHttpClient.Builder
的成員之一,它的文檔說自己是異步 HTTP 請求的執(zhí)行策略,現(xiàn)在看來,同步請求它也有摻和。getResponseWithInterceptorChain()
函數(shù)獲取 HTTP 返回結(jié)果,從函數(shù)名可以看出,這一步還會進行一系列“攔截”操作。dispatcher
自己已經(jīng)執(zhí)行完畢。dispatcher 這里我們不過度關(guān)注,在同步執(zhí)行的流程中,涉及到 dispatcher 的內(nèi)容只不過是告知它我們的執(zhí)行狀態(tài),比如開始執(zhí)行了(調(diào)用executed
),比如執(zhí)行完畢了(調(diào)用finished
),在異步執(zhí)行流程中它會有更多的參與。
真正發(fā)出網(wǎng)絡(luò)請求,解析返回結(jié)果的,還是getResponseWithInterceptorChain
:
private 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
在OkHttp 開發(fā)者之一介紹 OkHttp 的文章里面,作者講到:
the whole thing is just a stack of built-in interceptors.
可見Interceptor
是 OkHttp 最核心的一個東西,不要誤以為它只負責攔截請求進行一些額外的處理(例如 cookie),實際上它把實際的網(wǎng)絡(luò)請求、緩存、透明壓縮等功能都統(tǒng)一了起來,每一個功能都只是一個Interceptor
,它們再連接成一個Interceptor.Chain
,環(huán)環(huán)相扣,最終圓滿完成一次網(wǎng)絡(luò)請求。
從getResponseWithInterceptorChain
函數(shù)我們可以看到Interceptor.Chain
的分布依次是:
OkHttpClient
時設(shè)置的interceptors
;RetryAndFollowUpInterceptor
;BridgeInterceptor
;CacheInterceptor
;ConnectInterceptor
;OkHttpClient
時設(shè)置的networkInterceptors
;CallServerInterceptor
。在這里,位置決定了功能,最后一個 Interceptor 一定是負責和服務(wù)器實際通訊的,重定向、緩存等一定是在實際通訊之前的。
責任鏈模式在這個Interceptor
鏈條中得到了很好的實踐。
它包含了一些命令對象和一系列的處理對象,每一個處理對象決定它能處理哪些命令對象,它也知道如何將它不能處理的命令對象傳遞給該鏈中的下一個處理對象。該模式還描述了往該處理鏈的末尾添加新的處理對象的方法。
對于把Request
變成Response
這件事來說,每個Interceptor
都可能完成這件事,所以我們循著鏈條讓每個Interceptor
自行決定能否完成任務(wù)以及怎么完成任務(wù)(自力更生或者交給下一個Interceptor
)。這樣一來,完成網(wǎng)絡(luò)請求這件事就徹底從RealCall
類中剝離了出來,簡化了各自的責任和邏輯。兩個字:優(yōu)雅!
責任鏈模式在安卓系統(tǒng)中也有比較典型的實踐,例如 view 系統(tǒng)對點擊事件(TouchEvent)的處理。
回到 OkHttp,在這里我們先簡單分析一下ConnectInterceptor
和CallServerInterceptor
,看看 OkHttp 是怎么進行和服務(wù)器的實際通信的。
ConnectInterceptor
@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, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
實際上建立連接就是創(chuàng)建了一個HttpCodec
對象,它將在后面的步驟中被使用,那它又是何方神圣呢?它是對 HTTP 協(xié)議操作的抽象,有兩個實現(xiàn):Http1Codec
和Http2Codec
,顧名思義,它們分別對應(yīng) HTTP/1.1 和 HTTP/2 版本的實現(xiàn)。
在Http1Codec
中,它利用Okio對Socket
的讀寫操作進行封裝,Okio 以后有機會再進行分析,現(xiàn)在讓我們對它們保持一個簡單地認識:它對java.io
和java.nio
進行了封裝,讓我們更便捷高效的進行 IO 操作。
而創(chuàng)建HttpCodec
對象的過程涉及到StreamAllocation
、RealConnection
,代碼較長,這里就不展開,這個過程概括來說,就是找到一個可用的RealConnection
,再利用RealConnection
的輸入輸出(BufferedSource
和BufferedSink
)創(chuàng)建HttpCodec
對象,供后續(xù)步驟使用。
CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOException {
HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
httpCodec.finishRequest();
Response response = httpCodec.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
// 省略部分檢查代碼
return response;
}
我們抓住主干部分:
Response
對象;Response
對象;這里我們可以看到,核心工作都由HttpCodec
對象完成,而HttpCodec
實際上利用的是 Okio,而 Okio 實際上還是用的Socket
,所以沒什么神秘的,只不過一層套一層,層數(shù)有點多。
其實Interceptor
的設(shè)計也是一種分層的思想,每個Interceptor
就是一層。為什么要套這么多層呢?分層的思想在 TCP/IP 協(xié)議中就體現(xiàn)得淋漓盡致,分層簡化了每一層的邏輯,每層只需要關(guān)注自己的責任(單一原則思想也在此體現(xiàn)),而各層之間通過約定的接口/協(xié)議進行合作(面向接口編程思想),共同完成復(fù)雜的任務(wù)。
簡單應(yīng)該是我們的終極追求之一,盡管有時為了達成目標不得不復(fù)雜,但如果有另一種更簡單的方式,我想應(yīng)該沒有人不愿意替換。
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println(response.body().string());
}
});
// RealCall#enqueue
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
// Dispatcher#enqueue
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
這里我們就能看到 dispatcher 在異步執(zhí)行時發(fā)揮的作用了,如果當前還能執(zhí)行一個并發(fā)請求,那就立即執(zhí)行,否則加入readyAsyncCalls
隊列,而正在執(zhí)行的請求執(zhí)行完畢之后,會調(diào)用promoteCalls()
函數(shù),來把readyAsyncCalls
隊列中的AsyncCall
“提升”為runningAsyncCalls
,并開始執(zhí)行。
這里的AsyncCall
是RealCall
的一個內(nèi)部類,它實現(xiàn)了Runnable
,所以可以被提交到ExecutorService
上執(zhí)行,而它在執(zhí)行時會調(diào)用getResponseWithInterceptorChain()
函數(shù),并把結(jié)果通過responseCallback
傳遞給上層使用者。
這樣看來,同步請求和異步請求的原理是一樣的,都是在getResponseWithInterceptorChain()
函數(shù)中通過Interceptor
鏈條來實現(xiàn)的網(wǎng)絡(luò)請求邏輯,而異步則是通過ExecutorService
實現(xiàn)。
在上述同步(Call#execute()
執(zhí)行之后)或者異步(Callback#onResponse()
回調(diào)中)請求完成之后,我們就可以從Response
對象中獲取到響應(yīng)數(shù)據(jù)了,包括 HTTP status code,status message,response header,response body 等。這里 body 部分最為特殊,因為服務(wù)器返回的數(shù)據(jù)可能非常大,所以必須通過數(shù)據(jù)流的方式來進行訪問(當然也提供了諸如string()
和bytes()
這樣的方法將流內(nèi)的數(shù)據(jù)一次性讀取完畢),而響應(yīng)中其他部分則可以隨意獲取。
響應(yīng) body 被封裝到ResponseBody
類中,該類主要有兩點需要注意:
在2.2.1.2.發(fā)送和接收數(shù)據(jù):CallServerInterceptor小節(jié)中,我們就看過了 body 相關(guān)的代碼:
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
由HttpCodec#openResponseBody
提供具體 HTTP 協(xié)議版本的響應(yīng) body,而HttpCodec
則是利用 Okio 實現(xiàn)具體的數(shù)據(jù) IO 操作。
這里有一點值得一提,OkHttp 對響應(yīng)的校驗非常嚴格,HTTP status line 不能有任何雜亂的數(shù)據(jù),否則就會拋出異常,在我們公司項目的實踐中,由于服務(wù)器的問題,偶爾 status line 會有額外數(shù)據(jù),而服務(wù)端的問題也毫無頭緒,導(dǎo)致我們不得不忍痛繼續(xù)使用 HttpUrlConnection,而后者在一些系統(tǒng)上又存在各種其他的問題,例如魅族系統(tǒng)發(fā)送 multi-part form 的時候就會出現(xiàn)沒有響應(yīng)的問題。
在2.2.1.同步網(wǎng)絡(luò)請求小節(jié)中,我們已經(jīng)看到了Interceptor
的布局,在建立連接、和服務(wù)器通訊之前,就是CacheInterceptor
,在建立連接之前,我們檢查響應(yīng)是否已經(jīng)被緩存、緩存是否可用,如果是則直接返回緩存的數(shù)據(jù),否則就進行后面的流程,并在返回之前,把網(wǎng)絡(luò)的數(shù)據(jù)寫入緩存。
這塊代碼比較多,但也很直觀,主要涉及 HTTP 協(xié)議緩存細節(jié)的實現(xiàn),而具體的緩存邏輯 OkHttp 內(nèi)置封裝了一個Cache
類,它利用DiskLruCache
,用磁盤上的有限大小空間進行緩存,按照 LRU 算法進行緩存淘汰,這里也不再展開。
我們可以在構(gòu)造OkHttpClient
時設(shè)置Cache
對象,在其構(gòu)造函數(shù)中我們可以指定目錄和緩存大?。?/p>
public Cache(File directory, long maxSize);
而如果我們對 OkHttp 內(nèi)置的Cache
類不滿意,我們可以自行實現(xiàn)InternalCache
接口,在構(gòu)造OkHttpClient
時進行設(shè)置,這樣就可以使用我們自定義的緩存策略了。
OkHttp 還有很多細節(jié)部分沒有在本文展開,例如 HTTP2/HTTPS 的支持等,但建立一個清晰的概覽非常重要。對整體有了清晰認識之后,細節(jié)部分如有需要,再單獨深入將更加容易。
在文章最后我們再來回顧一下完整的流程圖:
OkHttpClient
實現(xiàn)Call.Factory
,負責為Request
創(chuàng)建Call
;RealCall
為具體的Call
實現(xiàn),其enqueue()
異步接口通過Dispatcher
利用ExecutorService
實現(xiàn),而最終進行網(wǎng)絡(luò)請求時和同步execute()
接口一致,都是通過getResponseWithInterceptorChain()
函數(shù)實現(xiàn);getResponseWithInterceptorChain()
中利用Interceptor
鏈條,分層實現(xiàn)緩存、透明壓縮、網(wǎng)絡(luò) IO 等功能;
文章題目:OkHttp解析
文章地址:http://aaarwkj.com/article6/godoig.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制網(wǎng)站、自適應(yīng)網(wǎng)站、用戶體驗、虛擬主機、品牌網(wǎng)站建設(shè)、移動網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)