OkHttp源碼分析

那么我今天給大家簡單地講一下Okhttp這款網(wǎng)絡(luò)框架及其原理。它是如何請求數(shù)據(jù),如何響應(yīng)數(shù)據(jù)的 ?有什么優(yōu)點?它的應(yīng)用場景是什么?6z


說到Okhtpp的原理他是基于原生的Http,他的優(yōu)點主要是如下幾點:

1.支持同步、異步。

2.支持GZIP減少數(shù)據(jù)流量

3.緩存響應(yīng)數(shù)據(jù)(從而減少重復(fù)的網(wǎng)絡(luò)請求)

4.自動重連(處理了代理服務(wù)問題和SSL握手失敗的問題)

5.支持SPDY(共享同一個Socket來處理同一個服務(wù)器的請求,如果SPDY不可用,則通過連接池來減少請求延時)


它的應(yīng)用場景呢是在數(shù)據(jù)量特別大重量級的網(wǎng)絡(luò)請求


上面說了一下他的優(yōu)點,下面說一下他是如何發(fā)起網(wǎng)絡(luò)請求如何響應(yīng)數(shù)據(jù)的


OkHttp中的重要類:

1,OkHttpClient:OkHttp請求客戶端,Builder模式實現(xiàn)

2,Dispatcher:本質(zhì)是異步請求的調(diào)度器,負(fù)責(zé)調(diào)度異步請求的執(zhí)行,控制最大請求并發(fā)數(shù)和單個主機(jī)的最大并發(fā)數(shù),并持有有一個線程池負(fù)責(zé)執(zhí)行異步請求,對同步請求只是作統(tǒng)計操作。

3,Request:封裝網(wǎng)絡(luò)請求,就是構(gòu)建請求參數(shù)(如url,header,請求方式,請求參數(shù)),Builder模式實現(xiàn)

4,Response:網(wǎng)絡(luò)請求對應(yīng)的響應(yīng),Builder模式實現(xiàn),真正的Response是通過RealCall.getResponseWithInterceptorChain()方法獲取的。

5,Call:是根據(jù)Request生成的一個具體的請求實例,且一個Call只能被執(zhí)行一次。

6,ConnectionPool:Socket連接池

7,Interceptor:Interceptor可以說是OkHttp的核心功能,它就是通過Interceptor來完成監(jiān)控管理,重寫和重試請求的。

8,Cache:可以自定義是否采用緩存,緩存形式是磁盤緩存,DiskLruCache。

不管是同步請求還是異步請求,都是通過RealCall.getResponseWithInterceptorChain()方法獲取請求結(jié)

果的,只不過在前者在主線程中執(zhí)行,而后者在線程池中的線程中執(zhí)行的。


一個典型的請求過程是這樣的,用一個構(gòu)造好的OkHttpClient和Request獲取到一個Call,然后執(zhí)行call的異步或者同步方法取得Response或者處理異常,如下所示:


OkHttpClient ?okHttpClient = new OkHttpClient();

Request request = new Request.Builder()

????????.url("")

????????.build();

okHttpClient.newCall(request).enqueue(new Callback() {

????@Override

????public void onFailure(Call call, IOException e) {


????}


????@Override

????public void onResponse(Call call, Response response) throws IOException {


????}

});

OkHttp3,網(wǎng)絡(luò)請求庫,同步請求RealCall.execute()和異步請求RealCall.enqueue(),請求任務(wù)都是交給Dispatcher調(diào)度請求任務(wù)的處理,請求通過一條攔截鏈,每一個攔截器處理一部分工作,最后一個攔截器,完成獲取請求任務(wù)的響應(yīng),會將響應(yīng)沿著攔截鏈向上傳遞。



這里實際上,Call的實現(xiàn)是一個RealCall的類,execute的代碼如下:

final class RealCall implements Call {

@Override public Response execute() throws IOException {

??synchronized (this) {

????if (executed) throw new IllegalStateException("Already Executed");

????executed = true;

??}

??try {

????client.dispatcher().executed(this);

????Response result = getResponseWithInterceptorChain(false);

????if (result == null) throw new IOException("Canceled");

????return result;

??} finally {

????client.dispatcher().finished(this);

??}

}

}


檢查這個call是否已經(jīng)被執(zhí)行了,每個call?只能被執(zhí)行一次

而enqueue實際上是RealCall的將內(nèi)部類AsyncCall扔進(jìn)了dispatcher中:client.dispatcher().enqueue(new AsyncCall(responseCallback));


public final class Dispatcher {

synchronized void enqueue(AsyncCall call) {

??if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {

????runningAsyncCalls.add(call);

????executorService().execute(call);

??} else {

????readyAsyncCalls.add(call);

??}

}


。AsyncCall實際上是一個Runnable,我們看一下進(jìn)入線程池后真正執(zhí)行的代碼:


final class AsyncCall extends NamedRunnable

public abstract class NamedRunnable implements Runnable

AsyncCall 實現(xiàn)NameRunnable接口,丹NameRunnable又實現(xiàn)了Runnable接口


?@Override protected void execute() {

????boolean signalledCallback = false;

????try {

??????Response response = getResponseWithInterceptorChain(forWebSocket);

??????if (canceled) {

????????signalledCallback = true;

????????responseCallback.onFailure(RealCall.this, new IOException("Canceled"));

??????} else {

????????signalledCallback = true;

????????responseCallback.onResponse(RealCall.this, response);

??????}

????} catch (IOException e) {

??????if (signalledCallback) {

????????// Do not signal the callback twice!

????????logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);

??????} else {

????????responseCallback.onFailure(RealCall.this, e);

??????}

????} finally {

??????client.dispatcher().finished(this);

????}

??}

}

?client.dispatcher().finished(this);這里邊this代表著AsycCall

于是這里需要介紹一個Dispatcher的概念。Dispatcher的本質(zhì)是異步請求的管理器,控制最大請求并發(fā)數(shù)和單個主機(jī)的最大并發(fā)數(shù),并持有一個線程池負(fù)責(zé)執(zhí)行異步請求。

對同步的請求只是用作統(tǒng)計。

他是如何做到控制并發(fā)呢,

其實原理就在上面的execute代碼里面,

真正網(wǎng)絡(luò)請求執(zhí)行前后會調(diào)用executed和finished方法,執(zhí)行和完成

而對于AsyncCall的finished方法后,

會根據(jù)當(dāng)前并發(fā)數(shù)目選擇是否執(zhí)行隊列中等待的AsyncCall。

并且如果修改Dispatcher的maxRequests或者maxRequestsPerHost也會觸發(fā)這個過程。最大請求主機(jī)

好的,

在回到RealCall中,我們看到無論是execute還是enqueue,真正的Response是通過這個函數(shù)getResponseWithInterceptorChain獲取的,

其他的代碼都是用作控制與回調(diào)。而這里就是真正請求的入口,也是到了OkHttp的一個很精彩的設(shè)計:Interceptor與Chain


上面分析到了,網(wǎng)絡(luò)請求的入口實質(zhì)上是在這里getResponseWithInterceptorChain


private?Response getResponseWithInterceptorChain() throws?IOException {

?// Build a full stack of interceptors. ???????????????????????????????????????????????????????????????????????????????????????????????????//構(gòu)建一個完整的攔截器堆棧 ?List 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(

????5.isForWebSocket()));


?Interceptor.Chain chain = new?RealInterceptorChain(

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

?return?chain.proceed(originalRequest);

}




下面這是OkHttp內(nèi)置攔截器的思維導(dǎo)圖↓





RetryAndFollowUpInterceptor ???后繼攔截器



@Override public Response intercept(Chain chain) throws IOException {

??Request request = chain.request();


??streamAllocation = new StreamAllocation(

??????client.connectionPool(), createAddress(request.url()), callStackTrace);

后繼攔截器 攔截

在OkHttp的RetryAndFollowUpInterceptor請求重定向的Interceptor中是根據(jù)當(dāng)前請求獲取的Response,來決定是否需要進(jìn)行重定向操作。在followUpRequest方法中,將會根據(jù)響應(yīng)userResponse,獲取到響應(yīng)碼,并從連接池StreamAllocation中獲取連接,然后根據(jù)當(dāng)前連接,得到路由配置參數(shù)Route。觀察以下代碼可以看出,這里通過userResponse得到了響應(yīng)碼responseCode。



接下來該方法會通過switch…case…來進(jìn)行不同的響應(yīng)碼處理操作。

從這段代碼開始,都是3xx的響應(yīng)碼處理,這里就開始進(jìn)行請求重定向的處理操作。





重試與重定向攔截器,用來實現(xiàn)重試和重定向功能,內(nèi)部通過while(true)死循環(huán)來進(jìn)行重試獲取Response(有重試上限,超過會拋出異常)。followUpRequest主要用來根據(jù)響應(yīng)碼來判斷屬于哪種行為觸發(fā)的重試和重定向(比如未授權(quán),超時,重定向等),然后構(gòu)建響應(yīng)的Request進(jìn)行下一次請求。當(dāng)然,如果沒有觸發(fā)重新請求就會直接返回Response。



當(dāng)網(wǎng)絡(luò)連接失敗時重試

當(dāng)服務(wù)器返回當(dāng)前請求需要進(jìn)行重定向是直接發(fā)起新的去請求,并在條件允許的情況下復(fù)用此鏈接。

主要做了三件事StreamAllocation:流量分配


[if !supportLists]?[endif]創(chuàng)建了StreamAllocation,用于Socket管理

[if !supportLists]?[endif]處理重定向

[if !supportLists]?[endif]失敗重連

BridgeInterceptor ????橋接攔截器


在請求階段自動補(bǔ)全請求頭,在響應(yīng)階段對GZIP進(jìn)行解壓縮

就是告訴服務(wù)器客戶端能夠接受的數(shù)據(jù)編碼類型,OKHTTP默認(rèn)就是 GZIP 類型



GZIP:

GZIP是網(wǎng)站壓縮加速的一種技術(shù),對于開啟后可以加快我們網(wǎng)站的打開速度,原理是經(jīng)過服務(wù)器壓縮,客戶端瀏覽器快速解壓的原理,可以大大減少了網(wǎng)站的流量。

Gzip開啟以后會將輸出到用戶瀏覽器的數(shù)據(jù)進(jìn)行壓縮的處理,這樣就會減小通過網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)量,提高瀏覽的速度。

是一種流行的文件壓縮算法,現(xiàn)在的應(yīng)用十分廣泛,尤其是在Linux平臺。當(dāng)應(yīng)用Gzip壓縮到一個純文本文件時,效果是非常明顯的,大約可以減少70%以上的文件大小。這取決于文件中的內(nèi)容。

BridgeInterceptor 功能主要有以下三點:

是負(fù)責(zé)將用戶構(gòu)建的一個Request請求轉(zhuǎn)化為能夠進(jìn)行網(wǎng)絡(luò)訪問的請求。



將這個符合網(wǎng)絡(luò)請求的Request進(jìn)行網(wǎng)絡(luò)請求。


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

將網(wǎng)絡(luò)請求回來的響應(yīng)Response轉(zhuǎn)化為用戶可用的 Response



Transfer-Encoding值為 chunked 表示請求體的內(nèi)容大小是未知的。



Host請求的 url 的主機(jī)



Connection默認(rèn)就是 "Keep-Alive",就是一個 TCP 連接之后不會關(guān)閉,保持連接狀態(tài)。



Accept-Encoding默認(rèn)是 "gzip" 告訴服務(wù)器客戶端支持 gzip 編碼的響應(yīng)。



Cookie當(dāng)請求設(shè)置了 Cookie 那么就是添加 Cookie 這個請求頭。



User-Agent "okhttp/3.4.1"這個值根據(jù) OKHTTP 的版本不一樣而不一樣,它表示客戶端 的信息。



上面就是將一個普通的Request添加很多頭信息,讓其成為可以發(fā)送網(wǎng)絡(luò)請求的 Request?


CacheInterceptor緩存攔截器

CacheInterceptor負(fù)責(zé)在request階段判斷是否有緩存,是否需要重新請求。在response階段負(fù)責(zé)把response緩存起來


緩存其實是一個非常復(fù)雜的邏輯,單獨的功能模塊,它其實不屬于OkHttp上的功能,只是通過Http協(xié)議和DiskLruCache做了處理而已。


DiskLruCache是Android提供的一個管理磁盤緩存的類。該類可用于在程序中把從網(wǎng)絡(luò)加載的數(shù)據(jù)

保存到磁盤上作為緩存數(shù)據(jù),例如一個顯示網(wǎng)絡(luò)圖片的gridView,可對從網(wǎng)絡(luò)加載的圖片進(jìn)行緩存,

提高程序的可用性。



剛才也提到了OkHttp的緩存攔截器不是屬于OkHttp上的功能,他是通過Http協(xié)議和DiskLruCache做的處理 ?一張圖來看一下緩存策略↓



ETag響應(yīng)頭部字段值是一個實體標(biāo)記,它提供一個“不透明”的緩存驗證器


兩種緩存的區(qū)別:

對于強(qiáng)制緩存,服務(wù)器通知瀏覽器一個緩存時間,在緩存時間內(nèi),下次請求,直接用緩存,不在時間內(nèi),執(zhí)行對比緩存策略。

對于對比緩存,將緩存信息中的Etag和Last-Modified通過請求發(fā)送給服務(wù)器,由服務(wù)器校驗,返回304狀態(tài)碼時,瀏覽器直接使用緩存。


HTTP的緩存規(guī)則是優(yōu)先考慮強(qiáng)制緩存,然后考慮對比緩存。

[if !supportLists]?[endif]首先判斷強(qiáng)制緩存中的數(shù)據(jù)的是否在有效期內(nèi)。如果在有效期,則直接使用緩存。如果過了有效期,則進(jìn)入對比緩存。

[if !supportLists]?[endif]在對比緩存過程中,判斷ETag是否有變動,如果服務(wù)端返回沒有變動,說明資源未改變,使用緩存。如果有變動,判斷Last-Modified。

[if !supportLists]?[endif]判斷Last-Modified,如果服務(wù)端對比資源的上次修改時間沒有變化,則使用緩存,否則重新請求服務(wù)端的數(shù)據(jù),并作緩存工作。


CacheStrategy緩存策略

直接看CacheStrategy的get方法。緩存策略是由請求和緩存響應(yīng)共同決定的。

[if !supportLists]?[endif]如果緩存響應(yīng)為空,則緩存策略為不使用緩存。

[if !supportLists]?[endif]如果請求是https但是緩存響應(yīng)沒有握手信息,同上不使用緩存。

[if !supportLists]?[endif]如果請求和緩存響應(yīng)都是不可緩存的,同上不使用緩存。

[if !supportLists]?[endif]如果請求是noCache,并且又包含If-Modified-Since或If-None-Match,同上不使用緩存。

[if !supportLists]?[endif]然后計算請求有效時間是否符合響應(yīng)的過期時間,如果響應(yīng)在有效范圍內(nèi),則緩存策略使用緩存。

[if !supportLists]?[endif]否則創(chuàng)建一個新的有條件的請求,返回有條件的緩存策略。

[if !supportLists]?[endif]如果判定的緩存策略的網(wǎng)絡(luò)請求不為空,但是只使用緩存,則返回兩者都為空的緩存策略。



CacheInterceptor的總體流程大致是:?

?著作權(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
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有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源碼分析-同步篇 很早就想拿okhttp開刀了,這次就記一次使用OKhttp的網(wǎng)絡(luò)請求。首先需要說明的...
    埃賽爾閱讀 987評論 1 2
  • OkHttp作為時下最受歡迎的網(wǎng)絡(luò)請求框架之一,它有著自己的優(yōu)點: 使用了眾多的設(shè)計模式(如:Builder模式、...
    永恒之眼V閱讀 304評論 1 1
  • 一、Okhttp的使用 1、創(chuàng)建一個OKHttpClient對象 2、創(chuàng)建一個request對象,通過內(nèi)部類Bui...
    千涯秋瑟閱讀 378評論 0 0
  • 好教師不僅僅是上好課那么簡單,還應(yīng)做一個超越者。 通過校本研修 引領(lǐng)教師做到三個超越 : 1、超越課堂界限,把教育...
    鄭國永閱讀 313評論 0 0
  • Reverse a String Factorialize a Number Check for Palindro...
    Rough33閱讀 287評論 0 0