深入理解OkHttp源碼及設(shè)計思想

前言

用OkHttp很久了,也看了很多人寫的源碼分析,在這里結(jié)合自己的感悟,記錄一下對OkHttp源碼理解的幾點心得。

整體結(jié)構(gòu)

網(wǎng)絡(luò)請求框架雖然都要做請求任務(wù)的封裝和管理,但是最大的難點在于網(wǎng)絡(luò)請求任務(wù)的多樣性,因為網(wǎng)絡(luò)層情況復(fù)雜,不僅要考慮功能性的建立Socket連接、文件流傳輸、TLS安全、多平臺等,還要考慮性能上的Cache復(fù)用、Cache過期、連接池復(fù)用等,這些功能如果交錯在一起,實現(xiàn)和維護(hù)都會有很大的問題。

為了解決這個問題,OkHttp采用了分層設(shè)計的思想,使用多層攔截器,每個攔截器解決一個問題,多層攔截器套在一起,就像設(shè)計模式中的裝飾者模式一樣,可以在保證每層功能高內(nèi)聚的情況下,解決多樣性的問題。

OkHttp使用了外觀模式,開發(fā)者直接操作的主要就是OkHttpClient,其實如果粗略劃分的話,整個OkHttp框架從功能上可以分為三部分:

1.請求和回調(diào):具體的類就是Call、RealCall(及其內(nèi)部類AsyncCall)、Callback等。

2.分發(fā)器及線程池:具體的類就是Dispatcher、ThreadPoolExecutor等。

3.攔截器:實現(xiàn)了分層設(shè)計+鏈?zhǔn)秸{(diào)用,具體的類就是Interceptor+RealInterceptorChain。

至于更具體的操作,均由攔截器實現(xiàn),包括應(yīng)用層攔截器、網(wǎng)絡(luò)層攔截器等,開發(fā)者也可以自己擴(kuò)展新的攔截器。

請求

網(wǎng)絡(luò)請求其實可以分為數(shù)據(jù)和行為兩部分,數(shù)據(jù)即我們的請求數(shù)據(jù)和返回數(shù)據(jù),行為則是發(fā)起網(wǎng)絡(luò)請求,以及得到處理結(jié)果。

數(shù)據(jù)(Request和Response)

在OkHttp中,用Request定義請求數(shù)據(jù),用Response定義返回數(shù)據(jù),這兩個類都使用了建造者模式,把對象的創(chuàng)建和使用分離開,但這兩個類更接近于數(shù)據(jù)模型,主要用來讀寫數(shù)據(jù),不做請求動作。

行為(Call/RealCall/AsyncCall和Callback)

在OkHttp中,用Call和Callback定義網(wǎng)絡(luò)請求,用Call去發(fā)起網(wǎng)絡(luò)請求,用Callback去接收異步返回,(如果是同步請求,就直接返回Response數(shù)據(jù))。

其中,Call是個接口,真正的實現(xiàn)類是RealCall,RealCall如果需要異步處理,還會先包裝為RealCall的內(nèi)部類AsyncCall,然后再把AsyncCall交給線程池。

在具體執(zhí)行過程中,把數(shù)據(jù)對象交給行為對象去操作:

在RealCall行為中調(diào)用enqueue去發(fā)起異步網(wǎng)絡(luò)請求,此時需要傳參Request數(shù)據(jù)對象;返回的Callback會傳遞Response數(shù)據(jù)對象。

如果RealCall行為中調(diào)用的是execute同步網(wǎng)絡(luò)請求,就直接返回Response數(shù)據(jù)對象。

RealCall只是對請求做了封裝,真正處理請求的是分發(fā)器Dispatcher。

分發(fā)器及線程池

對于網(wǎng)絡(luò)請求RealCall來說,需要可并行、可回調(diào)、可取消,因為OkHttp統(tǒng)一使用Dispatcher分發(fā)器來分發(fā)所有的Call請求,分發(fā)給多個線程進(jìn)行執(zhí)行(所以Dispatcher也叫反向代理),所以,這幾個問題就需要交給Dispatcher來處理,對于Dispatcher來說,可并行、可回調(diào)、可取消的問題可以進(jìn)一步被分解為以下幾個問題,并分別處理:

1.有沒有必要管理所有的請求

不論是同步請求還是異步請求,都是耗時操作,所以是個需要觀測的行為,比如請求結(jié)束需要處理,請求本身可能取消等,都需要管理起來。

而且,不論是正在運(yùn)行的,還是等待運(yùn)行的,都需要管理。

2.如何管理所有的請求

為了管理所有的請求,Dispatcher采用了隊列+生產(chǎn)+消費(fèi)的模式。

為同步執(zhí)行提供了runningSyncCalls來管理所有的同步請求;

為異步執(zhí)行提供了runningAsyncCalls和readyAsyncCalls來管理所有的異步請求。

其中readyAsyncCalls是在當(dāng)前可用資源不足時,用于緩存請求的。

由于這三個隊列的使用場景類似于棧,偶爾需要刪除功能,所以O(shè)kHttp使用了ArrayDeque雙端隊列來管理,ArrayDeque的設(shè)計和實現(xiàn)非常精妙,感興趣的可以深入了解一下。

http://www.lxweimin.com/p/132733115f95

3.如何確保多個隊列之間能順暢地調(diào)度

對于多線程情況下的隊列調(diào)度,其實就是數(shù)據(jù)移動和失敗阻塞的這兩個問題。

對于數(shù)據(jù)移動來說,就是要考慮多線程下隊列數(shù)據(jù)移動的問題。

對于同步請求來說,只有1個隊列,不存在數(shù)據(jù)移動,數(shù)據(jù)移動的場景在兩個異步隊列,每當(dāng)有一個異步請求finish了,就需要從待處理readyAsyncCalls隊列移動到runningAsyncCalls隊列,這在多線程場景下并不安全,需要加鎖:

synchronized?(this)?{//加鎖操作

if(!calls.remove(call))thrownewAssertionError("Call?wasn't?in-flight!");

if(promoteCalls)?promoteCalls();

runningCallsCount?=?runningCallsCount();

idleCallback?=this.idleCallback;

}

在promoteCalls時,會把call從ready隊列轉(zhuǎn)移到running隊列:

privatevoidpromoteCalls(){

if(runningAsyncCalls.size()?>=?maxRequests)return;//?Already?running?max?capacity.

...

for(Iterator?i?=?readyAsyncCalls.iterator();?i.hasNext();?)?{

AsyncCall?call?=?i.next();

if(runningCallsForHost(call)?<?maxRequestsPerHost)?{

i.remove();

runningAsyncCalls.add(call);//添加隊列

executorService().execute(call);//交給線程池

}

if(runningAsyncCalls.size()?>=?maxRequests)return;//?Reached?max?capacity.

}

}

另外這個移動的操作放在finish函數(shù)里,會存在另一個問題,就是如何確保會執(zhí)行這個finish函數(shù),避免造成失敗阻塞

對于失敗阻塞來說,因為網(wǎng)絡(luò)請求失敗是很常見的場景,必須能在失敗時避免阻塞隊列。

OkHttp的處理是為Call對象的execute函數(shù)寫try finally,在RealCall的execute函數(shù)里,在finally中調(diào)用client.dispatcher.finish(call),確保隊列不阻塞。

這其實類似AsyncTask的處理方式,AsyncTask也是使用了try finally,在finally中scheduleNext,確保隊列不阻塞。

4.如何實現(xiàn)多線程

io是個耗時但是不耗CPU的操作,是典型的需要并行處理的場景。

OkHttp不出意外地采用了線程池實現(xiàn)并行,這一點類似于AsyncTask,但不像AsyncTask使用了全局唯一的線程池,每個OkHttpClient都有自己的線程池。

不過,與AsyncTask不同的是,OkHttp的同步執(zhí)行不進(jìn)線程池,在RealCall執(zhí)行同步execute任務(wù)時,只是在Dispatcher的runningSyncCalls中記錄這個call,然后直接在當(dāng)前線程執(zhí)行了攔截器的操作。

至于異步執(zhí)行,就是在RealCall中enqueue時調(diào)用Dispatcher的enqueue,然后調(diào)用線程池executeService().execute(call),這里面的call是RealCall的內(nèi)部類AsyncCall,實現(xiàn)異步調(diào)用。

5.在這個過程中,用哪些方式提升效率

OkHttp主要針對隊列和線程池做了優(yōu)化:

循環(huán)數(shù)組

因為Dispatcher中的三個隊列需要頻繁出棧和入棧,所以采用了性能良好的循環(huán)數(shù)組ArrayDeque管理隊列。

阻塞隊列

因為Dispatcher自己用隊列管理了排隊的請求,所以Dispatcher中的線程池其實不需要緩存隊列,那么這個線程池的任務(wù)其實是盡快地把元素轉(zhuǎn)交給線程池中的io線程,所以采用了容量為0的阻塞隊列SynchronousQueue,SynchronousQueue與普通隊列不同,不是數(shù)據(jù)等線程,而是線程等數(shù)據(jù),這樣每次向SynchronousQueue里傳入數(shù)據(jù)時,都會立即交給一個線程執(zhí)行,這樣可以提高數(shù)據(jù)得到處理的速度。

控制線程數(shù)量

因為線程本身也會消耗資源,所以每個線程池都需要控制線程數(shù)量,OkHttp的線程池更進(jìn)一步,會針對每個Host主機(jī)的請求(避免全都卡死在某個Host上),分別控制線程數(shù)上限(5個),具體方法就是遍歷所有runningAsyncCall隊列中的每個Call,查詢每個Call的Host,并做計數(shù)。

攔截器原理

在前面的步驟中,不管是同步請求還是異步請求,最終都會調(diào)用攔截器來處理網(wǎng)絡(luò)請求。

//RealCall源碼

Response?result?=?getResponseWithInterceptorChain();

這就是OkHttp的核心,Interceptor攔截器。

在OkHttp中,Call、Callback和Dispatcher雖然很有用,但對于解決復(fù)雜的網(wǎng)絡(luò)請求沒有太多作用,使用了分層設(shè)計的攔截器Interceptor才是解決復(fù)雜網(wǎng)絡(luò)請求的核心,這也是OkHttp的核心設(shè)計。

分層設(shè)計

我們都知道,真實情況中的網(wǎng)絡(luò)行為其實非常復(fù)雜,縱跨軟件、協(xié)議、數(shù)據(jù)包、電信號、硬件等,所以網(wǎng)絡(luò)層的第一個基礎(chǔ)知識就是IOS七層模型,明確了各層的功能范圍,每一層各司其職,層與層依次依賴,實際上降低了開發(fā)和維護(hù)的難度與成本。

OkHttp也采用了分層設(shè)計思想,每層Interceptor的輸入都是Request,輸出都是Response,所以可以一層層地加工Request,再一層層地加工Response。

由于各個Interceptor之間不是組合關(guān)系,不能像ViewTree那樣遞歸調(diào)用,所以需要一個鏈把這些攔截器全部串起來,為此,入口RealCall會執(zhí)行網(wǎng)絡(luò)請求的getResponseWithInterceptorChain函數(shù),主要就是一層層地組織Interceptor,組成一個鏈,然后用chain.proceed去調(diào)用它。

ResponsegetResponseWithInterceptorChain()?throws?IOException{

//?Build?a?full?stack?of?interceptors.

List?interceptors?=newArrayList<>();

interceptors.addAll(client.interceptors());//自定義應(yīng)用攔截器

interceptors.add(retryAndFollowUpInterceptor);//重試/重定向

interceptors.add(newBridgeInterceptor(client.cookieJar()));//應(yīng)用請求轉(zhuǎn)網(wǎng)絡(luò)請求

interceptors.add(newCacheInterceptor(client.internalCache()));//緩存

interceptors.add(newConnectInterceptor(client));//連接

if(!forWebSocket)?{

interceptors.addAll(client.networkInterceptors());//自定義網(wǎng)絡(luò)攔截器

}

interceptors.add(newCallServerInterceptor(forWebSocket));//服務(wù)端連接

Interceptor.Chain?chain?=newRealInterceptorChain(//組成鏈

interceptors,null,null,null,0,?originalRequest);

returnchain.proceed(originalRequest);//從RealCall的Request開始鏈?zhǔn)教幚?/p>

}

如何實現(xiàn)鏈?zhǔn)教幚?/b>

我們看到,鏈?zhǔn)教幚淼娜肟谑荝ealInterceptorChain的proceed函數(shù):

publicResponseproceed(Request?request,?StreamAllocation?streamAllocation,?HttpCodec?httpCodec,

RealConnection?connection

)?throws?IOException{

...

RealInterceptorChain?next?=newRealInterceptorChain(//在chain中前進(jìn)一步

interceptors,?streamAllocation,?httpCodec,?connection,?index?+1,?request);

Interceptor?interceptor?=?interceptors.get(index);

Response?response?=?interceptor.intercept(next);//調(diào)用攔截器

...

returnresponse;

}

而攔截器在執(zhí)行過程中,會再調(diào)用chain

@Override

publicResponseintercept(Chain?chain)throwsIOException{

...

Response?networkResponse?=?chain.proceed(requestBuilder.build());

...

這樣,就形成一個chain.process(intreceptor)-->interceptor.intercept(chain)-->chainprocess(intreceptor)-->interceptor.intercept(chain)的循環(huán),這個過程中,chain不斷消費(fèi),直至最后一個攔截器,最后這個攔截器一定是CallServerInterceptor,CallServerInterceptor不再調(diào)用chain.process,鏈?zhǔn)秸{(diào)用結(jié)束。

攔截器的層次設(shè)計

了解過攔截器和鏈?zhǔn)椒磻?yīng)的基本原理,我們再來看看各攔截器的層次設(shè)計和具體實現(xiàn),有很多可以借鑒的地方。

我們先回到RealCall中,看看攔截器的層次和分類:

ResponsegetResponseWithInterceptorChain()?throws?IOException{

//?Build?a?full?stack?of?interceptors.

List?interceptors?=newArrayList<>();

interceptors.addAll(client.interceptors());//自定義應(yīng)用攔截器

interceptors.add(retryAndFollowUpInterceptor);//重試/重定向

interceptors.add(newBridgeInterceptor(client.cookieJar()));//應(yīng)用請求轉(zhuǎn)網(wǎng)絡(luò)請求

interceptors.add(newCacheInterceptor(client.internalCache()));//緩存

interceptors.add(newConnectInterceptor(client));//連接

if(!forWebSocket)?{

interceptors.addAll(client.networkInterceptors());//自定義網(wǎng)絡(luò)攔截器

}

interceptors.add(newCallServerInterceptor(forWebSocket));//實現(xiàn)在線網(wǎng)絡(luò)連接

Interceptor.Chain?chain?=newRealInterceptorChain(//組成鏈

interceptors,null,null,null,0,?originalRequest);

returnchain.proceed(originalRequest);//從RealCall的Request開始鏈?zhǔn)教幚?/p>

}

我們可以看到,OkHttp中攔截器的層次是這樣的:

1.自定義應(yīng)用攔截器

2.重試、重定向攔截器

3.應(yīng)用/網(wǎng)絡(luò)橋接攔截器

4.緩存攔截器

5.連接攔截器

6.自定義網(wǎng)絡(luò)攔截器

7.在線網(wǎng)絡(luò)請求攔截器

我們看到,我們開發(fā)者可以添加兩種自定義Interceptor,一種是client.interceptors()應(yīng)用層攔截器,一種是client.networkInterceptors()網(wǎng)絡(luò)層攔截器

但其實這兩種都是Interceptor,為什么可以分成是應(yīng)用層和網(wǎng)絡(luò)層呢?

因為在網(wǎng)絡(luò)層攔截器上方,是ConnectionInterceptor連接攔截器,這個攔截器里會提供Address、ConnectionPool等資源,可以用于處理網(wǎng)絡(luò)連接,networkInterceptors是添加在這之后的,可以參與真正的網(wǎng)絡(luò)層數(shù)據(jù)的處理。

接下來,我們自頂向下,依次看看每層攔截器的實現(xiàn)

攔截器——自定義應(yīng)用攔截器

OkHttp在最外圍允許添加自定義的應(yīng)用攔截器,我們可以攔截Request和Response,分別進(jìn)行加工,例如在Request時統(tǒng)一添加Header和Url參數(shù):

Request.Builder?builder?=?chain.request().newBuilder();

builder.addHeader("Accept-Charset","UTF-8");

builder.addHeader("Accept","?application/json");

builder.addHeader("Content-type","application/json");

HttpUrl?url=builder.build().url().newBuilder()

.addQueryParameter("mac",?EquipmentUtils.getMac())

.build();

Requestrequest=?builder.url(url).build();

還可以攔截Response內(nèi)容,打印返回數(shù)據(jù)的日志:

longt1?=?System.nanoTime();

Request?request?=?chain.request();

Response?response?=?chain.proceed(request);

longt2?=?System.nanoTime();

//直接復(fù)制字節(jié)流,獲取response的數(shù)據(jù)內(nèi)容

BufferedSource?sr?=?response.body().source();

sr.request(Long.MAX_VALUE);

Buffer?buf?=?sr.buffer().clone();//copy副本讀取,不能讀取原文

String?content?=?buf.readString(Charset.forName("UTF-8"));

buf.clear();

Log.i(TAG,"net?layer?received?response?of?url:?"+?request.url().url().toString()

+"\nresponse:?"+?content

+"\nspent?time:?"+?(t2?-?t1)?/1e6d);

開發(fā)者可以擴(kuò)展針對請求數(shù)據(jù)和返回數(shù)據(jù),自由開發(fā)功能。

攔截器——重試/重定向

雖然前面有開發(fā)者自定義的應(yīng)用攔截器,但是真正準(zhǔn)備處理網(wǎng)絡(luò)連接,是從OkHttp自己定義的RetryAndFollowUpInterceptor開始的,因為OkHttp正是把這個攔截器作為真正的入口,創(chuàng)建StreamAllocation對象,在StreamAllocation對象中準(zhǔn)備了網(wǎng)絡(luò)連接的Address、連接池等資源,后續(xù)的攔截器,使用的都是這個StreamAllocation對象。

StreanAllocation

StreamAllocation是OkHttp中用來定義和傳遞網(wǎng)絡(luò)資源,并建立網(wǎng)絡(luò)連接的對象,內(nèi)部包含:

Address:規(guī)定如何連接服務(wù)器,包括DNS、協(xié)議、URL等。

Route:存儲建立連接的目標(biāo)IP和端口InetSocketAddress,以及代理服務(wù)器。

ConnectionPool:存儲和復(fù)用已存在的連接,復(fù)用時根據(jù)Address查找對應(yīng)的連接。

StreamAllocation會通過findConnection創(chuàng)建連接,或復(fù)用已存在的連接,期間會調(diào)用RealConnection,根據(jù)設(shè)置建立TLS連接、處理握手協(xié)議等,最底層是根據(jù)當(dāng)前運(yùn)行的平臺,直接操作Socket。

每個Host不超過5個連接,每個連接不超過5分鐘。

重試/重定向

網(wǎng)絡(luò)環(huán)境本質(zhì)上是不穩(wěn)定的,已建立的連接可能突然不可用,或者連接可用但是服務(wù)器報錯,這就需要重試/重定向功能,這也是RetryAndFollowUpInterceptor攔截器的分層功能。

重試

如果整個鏈?zhǔn)秸{(diào)用出現(xiàn)了RouteException或IOException,就會調(diào)用recover函數(shù)重新建立連接;

重定向

如果服務(wù)器返回錯誤碼如301,要求重定向,就會調(diào)用followUpRequest函數(shù),新建一個Request,然后重定向,再走一遍整個調(diào)用鏈。

while

intercept函數(shù)中的這些主要邏輯都在while(true)循環(huán)中,最大循環(huán)上限是20。

攔截器——應(yīng)用轉(zhuǎn)網(wǎng)絡(luò)的橋接功能

BridgeInterceptor是個橋梁,這主要是指他會自動處理一些網(wǎng)絡(luò)層特有的Header信息,例如Host屬性,是HTTP1.1必須的,但應(yīng)用層并不關(guān)心這個屬性,這就是由BridgeInterceptor自動處理的。

BridgeInterceptor中處理的Header屬性包括Host、Connection的Keep-Alive、gzip透明壓縮、User-Agent描述、Cookie策略等。

當(dāng)然,因為OkHttp采用了外觀模式,所以很多屬性需要通過client設(shè)置和獲取。

攔截器——緩存功能

在網(wǎng)絡(luò)請求中使用緩存是非常必要提速手段,OkHttp專門用了CacheInterceptor攔截器來處理這個功能。

緩存的使用注意包括存儲、查詢和有效性檢查,在OkHttp中:

存儲,使用client外觀模式來設(shè)置存儲Cache數(shù)據(jù)的InternalCache實現(xiàn)類,在走請求鏈獲取Response時記錄cache。

查詢,在存儲Cache數(shù)據(jù)的InternalCache實現(xiàn)類中,根據(jù)Request過濾,來查找Cache。

有效性檢查,利用工具類CacheStrategy的getCandidate函數(shù),來判斷Cache數(shù)據(jù)的各項指標(biāo)是否達(dá)到條件。

攔截器——連接功能

在RetryAndFollowUpInterceptor入口處,我們已經(jīng)分析過,在OkHttp中,連接功能由StreamAlloc實現(xiàn),提供Address地址、Route路由、RealConnection連接、ConnectionPool線程池復(fù)用、身份驗證、協(xié)議、握手、平臺、安全等功能。

在ConnectionInterceptor這一層,其實還沒有真正連接網(wǎng)絡(luò),它的具體功能很簡單,就是準(zhǔn)備好request請求、streamAllocation連接資源、httpCodec傳輸工具、connection連接,為最底層的網(wǎng)絡(luò)連接服務(wù)。

其中,httpCodec通過sink提供了OKio封裝過的基于socket的OutputStream,通過source提供了OKio封裝的基于socket的InputStream,最終就是通過這個sink提交Request,用這個source獲取Response。

攔截器——自定義網(wǎng)絡(luò)攔截器

主要區(qū)別

自定義的網(wǎng)絡(luò)層攔截器相比應(yīng)用層攔截器,能直接監(jiān)測到在線網(wǎng)絡(luò)請求的數(shù)據(jù)交換過程。

例如,Http有url重定向機(jī)制,如果Http返回碼為301,就需要根據(jù)Header中Location字段的新url,重新發(fā)起一次請求,這樣的話,總共會有兩次請求。

在應(yīng)用層的攔截器看來,第一次請求并沒有返回有效數(shù)據(jù),它只會抓到一次請求,也就是第二次的請求。

但是在網(wǎng)絡(luò)層的攔截器看來,兩次都是網(wǎng)絡(luò)請求,所以它會抓到兩次請求。

用途擴(kuò)展

根據(jù)網(wǎng)絡(luò)層攔截器的特點,我們可以擴(kuò)展如下功能:

1.模擬各種網(wǎng)絡(luò)情況

網(wǎng)絡(luò)接口不只是可用不可用的問題,還存在速度波動的問題,一個穩(wěn)健的App應(yīng)該能hold住波動的甚至是斷斷續(xù)續(xù)的網(wǎng)絡(luò),但是這樣的網(wǎng)絡(luò)非常不好模擬,我們可以在網(wǎng)絡(luò)攔截器層自由設(shè)定網(wǎng)絡(luò)返回值和返回時間,輔助我們檢查App在處理網(wǎng)絡(luò)數(shù)據(jù)時的健壯性。

2.模擬多個備用地址切換

無論是為了災(zāi)備,還是為了節(jié)省DNS解析時間,App都會有多個備用地址,有些就是ip地址,當(dāng)網(wǎng)絡(luò)出現(xiàn)問題時,要自動切換到備用地址,就可以在網(wǎng)絡(luò)層模擬出301返回,直接重定向到備用地址。

3.模擬數(shù)據(jù)輔助開發(fā)/測試

在開發(fā)過程中,我們可以用gradle多環(huán)境的方法,增加一個mock的productFlavor,在這個環(huán)境下添加一個mockInterceptor,把指向官網(wǎng)的地址重定向為指向開發(fā)測試網(wǎng)址,甚至直接mock返回數(shù)據(jù),換掉在線數(shù)據(jù),這樣可以檢測整個網(wǎng)絡(luò)層的全部功能(編碼、緩存、切換、報錯等),把mock數(shù)據(jù)的內(nèi)容和App的反饋結(jié)合的話,還可以做到針對網(wǎng)絡(luò)數(shù)據(jù)的半自動/自動化的測試驗證。

攔截器——在線網(wǎng)絡(luò)請求功能

前面所有的攔截器,都是在準(zhǔn)備或處理網(wǎng)絡(luò)連接前后的數(shù)據(jù),只有CallServerInterceptor這個攔截器,是真正連接在線服務(wù)的。

它使用ConnectionInterceptor提供的HttpCodec傳輸工具來發(fā)出Request,獲取Response,然后用ResponseBuilder生成最終的Response,再層層傳遞給外層的攔截器。

HttpCodec本身是一個接口,實例是StreamAllocation利用RealConnection生產(chǎn)的,RealConnection根據(jù)連接池中的可用連接,利用Okio生產(chǎn)source和sink:

privatevoidconnectSocket(intconnectTimeout,intreadTimeout)throwsIOException{

Proxy?proxy?=?route.proxy();

Address?address?=?route.address();

rawSocket?=?proxy.type()?==?Proxy.Type.DIRECT?||?proxy.type()?==?Proxy.Type.HTTP

??address.socketFactory().createSocket()

:newSocket(proxy);

rawSocket.setSoTimeout(readTimeout);

...

//用Okio生產(chǎn)

source?=?Okio.buffer(Okio.source(rawSocket));

sink?=?Okio.buffer(Okio.sink(rawSocket));

...

}

Okio的source是socket.inputStream,sink是socket.outputStream。

所以,真正在傳輸數(shù)據(jù)時,就是用Okio的sink去傳socket,用source去取socket,底層其實也是socket操作。

其他特性

以上是OkHttp的主要內(nèi)容,此外,OkHttp還有一些很有意思的特性。

1.返回數(shù)據(jù)閱后即焚

在OkHttp中,如果要攔截ResponseBody的數(shù)據(jù)內(nèi)容(比如寫日志),會發(fā)現(xiàn)該數(shù)據(jù)讀過一次就會被情況,相當(dāng)于是“閱后即焚:

//ResponseBody源碼

publicfinalStringstring()throwsIOException{//底層不能自己消化異常,應(yīng)該向上層拋出異常

BufferedSource?source?=?source();

try{

Charset?charset?=?Util.bomAwareCharset(source,?charset());

returnsource.readString(charset);

//不做catch,異常全部拋出給上層

}finally{//確保原始字節(jié)數(shù)據(jù)得到處理

Util.closeQuietly(source);//閱后即焚,這樣可以迅速騰出內(nèi)存空間來

}

}

如果一定要攔截出數(shù)據(jù)內(nèi)容,我們就不能直接讀ResponseBody中的source,需要copy一個副本才行:

BufferedSource?sr?=?response.body().source();

sr.request(Long.MAX_VALUE);

Buffer?buf?=?sr.buffer().clone();//copy副本讀取,不能讀取原文

String?content?=?buf.readString(Charset.forName("UTF-8"));

buf.clear();

Response也提供了專門獲取ResponsBody數(shù)據(jù)的函數(shù)peekBody,實現(xiàn)原理也是copy:

//Response源碼

publicResponseBodypeekBody(longbyteCount)throwsIOException{

BufferedSource?source?=?body.source();

source.request(byteCount);

Buffer?copy?=?source.buffer().clone();

...

returnResponseBody.create(body.contentType(),?result.size(),?result);

}

參考

深入解析OkHttp3

OkHttp3源碼分析[綜述]

Okhttp-wiki 之 Interceptors 攔截器

如果您覺得不錯,請別忘了轉(zhuǎn)發(fā)、分享、點贊讓更多的人去學(xué)習(xí),在順便給大家推薦一個架構(gòu)交流群:617434785,里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化這些成為架構(gòu)師必備的知識體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源。相信對于已經(jīng)工作和遇到技術(shù)瓶頸的碼友,在這個群里會有你需要的內(nèi)容。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,916評論 2 372

推薦閱讀更多精彩內(nèi)容

  • 用OkHttp很久了,也看了很多人寫的源碼分析,在這里結(jié)合自己的感悟,記錄一下對OkHttp源碼理解的幾點心得。 ...
    藍(lán)灰_q閱讀 4,325評論 4 34
  • 那么我今天給大家簡單地講一下Okhttp這款網(wǎng)絡(luò)框架及其原理。它是如何請求數(shù)據(jù),如何響應(yīng)數(shù)據(jù)的 有什么優(yōu)點?它的應(yīng)...
    卓而不群_0137閱讀 320評論 0 1
  • OkHttp源碼的samples的簡單使用的示例: public static void main(String....
    _warren閱讀 791評論 0 1
  • 希望你一切都好 時間在變,人也在變。 生命是一場無法回放的絕版電影,有些事,不管你如何努力,回不去就是回不去了。 ...
    設(shè)計與故事閱讀 345評論 0 1
  • 管理者的最終目標(biāo)就是要達(dá)成業(yè)績,因而所有管理行為都應(yīng)當(dāng)聚集在這個唯一的目標(biāo)上。因為對于任何企業(yè)、任何團(tuán)隊來說,業(yè)績...
    Kingofcool閱讀 233評論 0 0