首先声功,閱讀完本文希望你能回答:
ok(指的okhttp)它是怎么發(fā)起(創(chuàng)建和建立)http請求的呢?
ok它是怎么創(chuàng)建call的呢宠叼?
ok里面HttpCodec對象是啥先巴?
ok它是怎么進(jìn)行和服務(wù)器實際通信的?
ok它是怎么樣實現(xiàn)同步網(wǎng)絡(luò)和一部網(wǎng)絡(luò)請求的冒冬?代碼的結(jié)構(gòu)和設(shè)計模式方面你得到了什么么伸蚯?
廢話不多說,ok它提供okhttpclient()
public OkHttpClient() { this(new Builder()); }
方便我們使用简烤,提供快捷操作剂邮,全部使用默認(rèn)配置。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;
}
它是怎么發(fā)起http請求的呢挥萌?(涉及到創(chuàng)建和建立)
看里面的run()方法
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,負(fù)責(zé)根據(jù)請求創(chuàng)建新的call枉侧。
callFactory 負(fù)責(zé)創(chuàng)建 HTTP 請求引瀑,HTTP 請求被抽象為了 okhttp3.Call 類,它表示一個已經(jīng)準(zhǔn)備好榨馁,可以隨時執(zhí)行的 HTTP 請求
它是怎么創(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);
}
A.發(fā)現(xiàn)跟源碼,出現(xiàn)了realcall這個類翼虫。接下來了解下它的同時屑柔,分析下同步網(wǎng)絡(luò)請求的過程
realcall里面的 excute方法:
@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 = getResponseWithInterceptor();//(3)
if (result == null) throw new IOException("Canceled"); return result; }
finally {client.dispatcher().finished(this);// (4) } }
相信大家也看到了1,2蛙讥,3锯蛀,4了不賣關(guān)子灭衷,這里做的4件事情是在干什么呢次慢?
1.檢查:查call是否已經(jīng)執(zhí)行,每個call只能被執(zhí)行一次,如果想要一個一摸一樣的call迫像,也提供了clone的方法劈愚。
2.執(zhí)行:dispatcher是 上面說的OkHttpClient.Builder成員之一。
看源碼的時候這個類說自己是異步http請求的執(zhí)行策略闻妓,現(xiàn)在再看菌羽,同步tm也摻合進(jìn)來了
3.結(jié)果:調(diào)用這個函數(shù)獲取htto的返回結(jié)果,函數(shù)名稱上來看由缆,這一步還會進(jìn)行一系列的“攔截”操作(getResponseWithInterceptorChain())
4.over:通知dispatcher自己執(zhí)行完成
說了上面四點注祖,又發(fā)現(xiàn)什么么?
Policy on when async requests are executed. Each dispatcher uses an {@link ExecutorService}
to run calls internally. If you supply your own executor,
it should be able to run {@linkplain getMaxRequests the configured maximum}
number of calls concurrently.
dispatcher注釋(ok的任務(wù)隊列的管理與調(diào)度其實就是Dispatcher一個類來完成均唉,雖然我們只是針對異步任務(wù)來講解是晨,但是它也負(fù)責(zé)同步任務(wù)的維護(hù),如 executed()方法的調(diào)用標(biāo)識任務(wù)的開始舔箭,finished()方法的調(diào)用標(biāo)識任務(wù)結(jié)束罩缴,具體代碼可以自行查閱。在任務(wù)被調(diào)度執(zhí)行以后层扶,任務(wù)就需要去執(zhí)行了箫章,也就是請求流程的執(zhí)行過程。本篇文章的最后一部分對此做介紹镜会,這一部分也是很多文章都會重點介紹的interceptor的調(diào)用流程檬寂,在okhttp中所有的功能幾乎都是通過定義interceptor, 對request和response做操作來實現(xiàn)的稚叹,其實就也就是請求流程的執(zhí)行過程焰薄。)
真正發(fā)出網(wǎng)絡(luò)請求的,解析返回結(jié)果的還是getResponseWithInterceptorChain:
先看圖:
一個接力傳遞塞茅,為了描述涉及到兩個類的遞歸過程
A concrete interceptor chain that carries the entire interceptor chain:
all application \* interceptors, the OkHttp core, all network interceptors,
and finally the network caller.
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ā)者之一的作者有寫到:the whole thing is just a stack of built-in interceptors.
可見攔截器是okhttp最核心的一個,不要誤以為它只負(fù)責(zé)攔截請求進(jìn)行了一些額外的處理(如cookie)季率,實質(zhì)上它吧網(wǎng)絡(luò)請求野瘦,緩存,透明壓縮等功能都同意了起來飒泻。每一個功能都是一個攔截器Interceptor鞭光,它們再連接一個chain。最后才完成一次網(wǎng)絡(luò)請求
從getResponseWithInterceptorChain函數(shù)可以看到泞遗,Interceptor.Chain
的分布依次是:
這里用網(wǎng)上的一個截圖來表示流程。
在配置okhttpclient時設(shè)置的interceptors
負(fù)責(zé)失敗重回 i以及重定向的retryandfollowupinterceptor
負(fù)責(zé)吧用戶構(gòu)造請求轉(zhuǎn)發(fā)送到服務(wù)器請求史辙,吧度武器返回的響應(yīng)轉(zhuǎn)換成用戶友好的響應(yīng)的bridgerintecptor
負(fù)責(zé)讀取緩存直接返回汹买,更新緩存的cache佩伤;和服務(wù)器建立連接的connect
配置okhttpclient時設(shè)置的network;負(fù)責(zé)向服務(wù)器發(fā)送請求數(shù)據(jù)晦毙,從服務(wù)器讀取響應(yīng)數(shù)據(jù)的callserver
這個讓我看到了一個責(zé)任鏈模式生巡!不得不說okhttp寫出來真的花費了很多構(gòu)思和心血!
它包含了一些命令對象和一系列的處理對象见妒,每一個處理對象決定它能處理哪些命令對象孤荣,它也知道如何將它不能處理的命令對象傳遞給該鏈中的下一個處理對象。該模式還描述了往該處理鏈的末尾添加新的處理對象的方法须揣。--stay
責(zé)任鏈模式在這個 Interceptor 鏈條中得到了很好的實踐Q喂伞!耻卡!學(xué)習(xí)了
接下來遂庄,還有更好的!這個忍不住要說“優(yōu)雅”劲赠,“華麗”涛目!我要說的就是對于request變成response這件事,每個攔截器都有可能完成這件事的流程框架設(shè)計凛澎。(這個鏈條讓讓每個攔截器自己hi還是傳遞給下一個攔截器去hi很靈活芭巍!)
這樣一來塑煎,完成網(wǎng)絡(luò)的請求徹底從realcall這個類中隔離開了沫换,簡化了它們各自的職能。
其實最铁,view的事件分發(fā)中對touchevent事件的處理就是比較典型的責(zé)任鏈讯赏!
好了接下來帶著問題:OkHttp 是怎么進(jìn)行和服務(wù)器實際通信的?
簡單的看下建立連接的connectinterceptor和callserverinteceptor
Connectinterceptor
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();//連接與流的橋梁冷尉, 它負(fù)責(zé)為一次請求尋找連接并建立流漱挎,從而完成遠(yuǎn)程通信,所以StreamAllocation與請求雀哨,連接磕谅,流都相關(guān)
// 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 對象,它將在后面被拿出來使用雾棺,那它又是啥呢膊夹?是
/** Encodes HTTP requests and decodes HTTP responses. */
有兩個實現(xiàn):Http1Codec 和 Http2Codec,它們分別對應(yīng) HTTP/1.1 和 HTTP/2 版本的實現(xiàn)捌浩。
在 Http1Codec 中放刨,它利用 Okio 對 Socket 的讀寫操作進(jìn)行封裝,(Okio它對 java.io 和 java.nio 進(jìn)行了封裝尸饺,更便捷高效的進(jìn)行 IO 操作进统。)
而創(chuàng)建 HttpCodec 對象的過程涉及到 StreamAllocation拓诸、RealConnection,代碼較長麻昼,這里就不詳述,這個過程概括來說馋辈,就是:找到一個可用的 RealConnection抚芦,再利用 RealConnection 的輸入輸出(BufferedSource 和 BufferedSink)創(chuàng)建 HttpCodec 對象,供后續(xù)步驟使用迈螟。
Callserverinteceptor
@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); //1. 向socket中寫入請求header信息
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
//2. 向socket中寫入請求body信息
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
httpCodec.finishRequest(); //3. 完成網(wǎng)絡(luò)請求的寫入
//4. 讀取網(wǎng)絡(luò)響應(yīng)header信息
Response response = httpCodec.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
if (!forWebSocket || response.code() != 101) {
//5由 HttpCodec#openResponseBody 提供具體 HTTP 協(xié)議版本的響應(yīng) body叉抡,而 HttpCodec 則是利用 Okio實現(xiàn)具體的數(shù)據(jù) IO 操作。
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
// 省略部分檢查代碼
return response;
}
可以看出核基本上都時由 HttpCodec 對象完成答毫,而 HttpCodec 實際上利用的是 Okio褥民,而 Okio (https://github.com/square/okio)實際上還是用的 Socket。
B.接下來發(fā)起異步請求:
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());
//響應(yīng) body 被封裝到 ResponseBody 類中洗搂,該類主要有兩點需要注意:每個 body 只能被消費一次消返,多次消費會拋出異常;body 必須被關(guān)閉耘拇,否則會發(fā)生資源泄漏撵颊;
}
});
// 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);
}
}
可以發(fā)現(xiàn)dispatcher異步的時候,如果還能執(zhí)行一個并發(fā)請求就執(zhí)行惫叛,否則加入到對列倡勇,而正在執(zhí)行的請求執(zhí)行完就回調(diào)用promotecalls函數(shù),來吧對列中的asynccall變?yōu)閞unningasynccall嘉涌,并且開始執(zhí)行F扌堋(這里的 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)法严。
C.緩存策略方面這里也不過多介紹了损敷,
(主要涉及 HTTP 協(xié)議緩存細(xì)節(jié)的實現(xiàn),而具體的緩存邏輯 OkHttp 內(nèi)置封裝了一個 Cache 類深啤,它利用 DiskLruCache拗馒,用磁盤上的有限大小空間進(jìn)行緩存,按照 LRU 算法進(jìn)行緩存淘汰)
我們可以在構(gòu)造 OkHttpClient 時設(shè)置 Cache 對象溯街,在其構(gòu)造函數(shù)中我們可以指定目錄和緩存大杏展稹:
public Cache(File directory, long maxSize);
自定義的緩存策略:也可以自行實現(xiàn) InternalCache 接口洋丐,在構(gòu)造 OkHttpClient 時進(jìn)行設(shè)置。
D.對整體有了清晰認(rèn)識之后挥等,細(xì)節(jié)部分如有需要友绝,再單獨深入將更加容易。(這里借鑒piasy大神的圖和總結(jié))
- OkHttpClient 實現(xiàn) Call.Factory迁客,負(fù)責(zé)為 Request 創(chuàng)建 Call;
- RealCall 為具體的 Call 實現(xiàn)辞槐,其 enqueue() 異步接口通過 Dispatcher 利用 ExecutorService 實現(xiàn)掷漱,而最終進(jìn)行網(wǎng)絡(luò)請求時和同步 execute() 接口一致,都是通過 getResponseWithInterceptorChain() 函數(shù)實現(xiàn)榄檬;
- getResponseWithInterceptorChain() 中利用 Interceptor 鏈條卜范,分層實現(xiàn)緩存、透明壓縮鹿榜、網(wǎng)絡(luò) IO 等功能海雪;