Android-網(wǎng)絡(luò)請(qǐng)求庫(kù)okhttp源碼閱讀隨筆

一:先看看okhttp簡(jiǎn)單的配置以及使用:

1. 在app的module中先配置依賴 ?implementation'com.squareup.okhttp3:okhttp:3.10.0'

2.okhttp執(zhí)行網(wǎng)絡(luò)請(qǐng)求調(diào)用方式:

????異步請(qǐng)求調(diào)用Call.enqueue()早抠;同步請(qǐng)求調(diào)用Call.execute()

Activity

二:框架基本流程源碼剖析

由于OkhttpClient內(nèi)部有非常復(fù)雜且多的參數(shù)配置憋活,作為一個(gè)框架來(lái)說(shuō)驾讲,為了讓用戶使用起來(lái)比較友好秸妥,采用了建造者模式逗载,來(lái)構(gòu)建OkhttpClient所需要的一些重要的參數(shù)配置項(xiàng),這里就不用多說(shuō)了。

然后我們先分析下一步代碼Call call = okHttpClient.newCall(request);這里通過(guò)newCall得到了一個(gè)Call對(duì)象睡腿,根據(jù)代碼調(diào)用鏈可以看出,newCall實(shí)際創(chuàng)建的是一個(gè)名叫RealCall的對(duì)象峻贮。????????

/**

* Prepares the {@code request} to be executed at some point in the future.

*/

@Override public CallnewCall(Request request) {

return RealCall.newRealCall(this, request, false /* for web socket */);

}

static RealCallnewRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {

// Safely publish the Call instance to the EventListener.

? RealCall call =new RealCall(client, originalRequest, forWebSocket);

? call.eventListener = client.eventListenerFactory().create(call);

? return call;

}

然后我們繼續(xù)跟進(jìn)席怪,先看下enqueue這個(gè)異步請(qǐng)求究竟是怎么處理的

@Override public void enqueue(Callback responseCallback) {

synchronized (this) {

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

? ? executed =true;

? }

captureCallStackTrace();

? eventListener.callStart(this);

? client.dispatcher().enqueue(new AsyncCall(responseCallback));

}

這里我們可以看到,他在這里做了個(gè)判斷纤控,如果同一個(gè)Call對(duì)象調(diào)用了多次enqueue挂捻,這里回拋出異常。接下來(lái)看關(guān)鍵代碼船万,最終okhttp框架將Callback(也就是我們請(qǐng)求的時(shí)候傳進(jìn)來(lái)的回調(diào)函數(shù))包裝為了一個(gè)AysncCall(這個(gè)AysncCall實(shí)際是一個(gè)Runnable细层,最終由Dispatcher類中維護(hù)的線程池中的線程執(zhí)行)惜辑,交給了一個(gè)叫做Dispatcher的類,我們繼續(xù)跟進(jìn)...

synchronized void enqueue(AsyncCall call) {

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

runningAsyncCalls.add(call);

? ? executorService().execute(call);

? }else {

readyAsyncCalls.add(call);

? }

}

Dispatcher

這里可以看到Dispatcher類里面維護(hù)了三個(gè)隊(duì)列疫赎,包含運(yùn)行中的異步請(qǐng)求隊(duì)列runningAysncCalls,運(yùn)行中的同步請(qǐng)求隊(duì)列runningSyncCalls碎节,以及待運(yùn)行的異步請(qǐng)求隊(duì)列readyAsyncCalls捧搞。在調(diào)用enqueue的時(shí)候,框架層先判斷正在運(yùn)行中的異步隊(duì)列runningAysncCalls的個(gè)數(shù)是否小于最大請(qǐng)求數(shù)maxRequests(默認(rèn)64)狮荔,并且判斷這個(gè)即將添加的請(qǐng)求的host在runningAysncCalls中是否小于maxRequestsPerHost(默認(rèn)5)胎撇,都滿足則將這個(gè)封裝由請(qǐng)求信息的Call對(duì)象添加到runningAysncCalls,并且交給線程池executorService執(zhí)行殖氏,不滿足則添加到準(zhǔn)備隊(duì)列readyAsyncCalls晚树。剛才已經(jīng)說(shuō)過(guò)了AsyncCall是一個(gè)Runnable,也就是最終由線程執(zhí)行到AsyncCall的execute方法雅采,繼續(xù)...


RealCall$$AsyncCall

這里可以看到execute方法最終是通過(guò)getResponseWithInterceptorChain()得到Response爵憎,然后通過(guò)我們?cè)趀nqueue時(shí)傳進(jìn)來(lái)的那個(gè)Callback將結(jié)果回調(diào)出去。追蹤同步請(qǐng)求代碼調(diào)用鏈也發(fā)現(xiàn)婚瓜,最終也是這個(gè)方法返回得到Response宝鼓,那么接下來(lái)看看getResponseWithInterceptorChain

RealCall

從getResponseWithInterceptorChain這個(gè)方法,框架是將一些Interceptor添加到一個(gè)list中巴刻,然后創(chuàng)建了一個(gè)RealInterceptorChain愚铡,調(diào)用了它的proceed方法

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,

? ? RealConnection connection)throws IOException {

//************省略代碼

// 這里又創(chuàng)建了一個(gè)RealInterceptorChain,不過(guò)這里有個(gè)關(guān)鍵參數(shù)不一樣胡陪,那就是index +1沥寥,這里邏輯是先處理攔截器集合的Interceptor的intercept方法,處理每個(gè)攔截器自己的邏輯柠座,然后通過(guò)index+1邑雅,取下一個(gè)攔截器執(zhí)行interceptor.intercept

? RealInterceptorChain next =new RealInterceptorChain(interceptors, streamAllocation, httpCodec,

? ? ? connection, index +1, request, call, eventListener, connectTimeout, readTimeout,

? ? ? writeTimeout);

? Interceptor interceptor =interceptors.get(index);

? Response response = interceptor.intercept(next);

//************省略代碼

return response;

}

其實(shí)這里是一個(gè)責(zé)任鏈模式的一個(gè)應(yīng)用,okhttp框架將網(wǎng)絡(luò)請(qǐng)求的一些步驟封裝成了好幾層(也就是攔截器interceptor)愚隧,根據(jù)之前上面getResponseWithInterceptorChain的截圖可以看到蒂阱,有以下,簡(jiǎn)單描述下各自負(fù)責(zé)的內(nèi)容:

RetryAndFollowUpInterceptor:網(wǎng)絡(luò)請(qǐng)求重試以及重定向請(qǐng)求

BridgeInterceptor:主要處理請(qǐng)求里面Header的相關(guān)狂塘,包括gzip的壓縮解壓縮录煤,cookie等

CacheInterceptor:這里做了緩存相關(guān)策略,比如沒(méi)網(wǎng)絡(luò)時(shí)但有緩存數(shù)據(jù)荞胡,可以直接返回妈踊,還比如說(shuō)后臺(tái)返回的數(shù)據(jù)給了時(shí)效性,下次請(qǐng)求的時(shí)候看到緩存數(shù)據(jù)有效泪漂,這個(gè)時(shí)候直接返回緩存數(shù)據(jù)廊营,節(jié)省了網(wǎng)絡(luò)開(kāi)銷(xiāo)

ConnectInterceptor:這里面有連接池connectionPool歪泳,okhttp是基于socket的一個(gè)封裝,這里有socket連接的緩存

CallServerInterceptor:這里做了最終的網(wǎng)絡(luò)請(qǐng)求操作與服務(wù)端交互

當(dāng)然露筒,這里可以根據(jù)自己的需求自定義攔截器呐伞,實(shí)現(xiàn)自己的邏輯。然后最終的響應(yīng)Response通過(guò)回調(diào)返回后(代碼在AsyncCall#execute())慎式,在finally中調(diào)用了client.dispatcher().finished(this);這個(gè)方法所做的事是從runningAysncCalls中移除這個(gè)已經(jīng)完成的請(qǐng)求伶氢,如果條件滿足,將readyAsyncCalls中的請(qǐng)求添加到runningAysncCalls隊(duì)列中并執(zhí)行

private void finished(Deque calls, T call, boolean promoteCalls) {

int runningCallsCount;

? Runnable idleCallback;

? synchronized (this) {

if (!calls.remove(call))throw new AssertionError("Call wasn't in-flight!");

? ? if (promoteCalls) promoteCalls();

? ? runningCallsCount = runningCallsCount();

? ? idleCallback =this.idleCallback;

? }

if (runningCallsCount ==0 && idleCallback !=null) {

idleCallback.run();

? }

}

private void promoteCalls() {

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

? if (readyAsyncCalls.isEmpty())return; // No ready calls to promote.

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

AsyncCall call = i.next();

? ? if (runningCallsForHost(call)

i.remove();

? ? ? runningAsyncCalls.add(call);

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

? ? }

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

? }

}

至此瘪吏,關(guān)于okhttp一個(gè)完整的請(qǐng)求癣防,基本梳理完成。內(nèi)部的一些攔截器相關(guān)內(nèi)容掌眠,后續(xù)有時(shí)間再深挖蕾盯。

這里有一些點(diǎn)需要注意:

1.關(guān)于okhttp里面Dispatcher的線程池創(chuàng)建,這里采用的是SynchronousQueue<Runnable>,采用這個(gè)同步隊(duì)列的原因是希望更快的將runnable交給線程池里面的線程去處理蓝丙,一般來(lái)說(shuō)级遭,SynchronousQueue的size<=1。

2.Dispatcher的線程池線程數(shù)量最大為Integer.MAX_VALUE迅腔,疑問(wèn):這里不設(shè)上限装畅,會(huì)不會(huì)有性能問(wèn)題?答:這里其實(shí)不會(huì)的沧烈,雖然這里沒(méi)做控制但是runningAsyncCalls這個(gè)執(zhí)行中隊(duì)列有做上限處理掠兄,所以不用擔(dān)心。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锌雀,一起剝皮案震驚了整個(gè)濱河市蚂夕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腋逆,老刑警劉巖婿牍,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惩歉,居然都是意外死亡等脂,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)撑蚌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)上遥,“玉大人,你說(shuō)我怎么就攤上這事争涌》鄢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)模软。 經(jīng)常有香客問(wèn)我伟骨,道長(zhǎng),這世上最難降的妖魔是什么燃异? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任携狭,我火速辦了婚禮,結(jié)果婚禮上回俐,老公的妹妹穿的比我還像新娘暑中。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布眼刃。 她就那樣靜靜地躺著莽囤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪万皿。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音政冻,去河邊找鬼。 笑死线欲,一個(gè)胖子當(dāng)著我的面吹牛明场,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播李丰,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼苦锨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了趴泌?” 一聲冷哼從身側(cè)響起舟舒,我...
    開(kāi)封第一講書(shū)人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嗜憔,沒(méi)想到半個(gè)月后秃励,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吉捶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年夺鲜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呐舔。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡币励,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滋早,到底是詐尸還是另有隱情榄审,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布杆麸,位于F島的核電站搁进,受9級(jí)特大地震影響浪感,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饼问,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一影兽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧莱革,春花似錦峻堰、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至闹击,卻和暖如春镶蹋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赏半。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工贺归, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人断箫。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓拂酣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親仲义。 傳聞我的和親對(duì)象是個(gè)殘疾皇子婶熬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359