最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

DEVLOG 12.30 網(wǎng)絡(luò)請求框架(上)-- OkHttp中的Dispatcher

2021-12-30 21:33 作者:房頂上的鋁皮水塔  | 我要投稿

參考內(nèi)容:?Android程序員中高級進(jìn)階學(xué)習(xí)/OkHttp原理分析

寫在前面:2021年就要結(jié)束了,這一年我從九月份開始寫專欄,我把這里當(dāng)成了自己的云上筆記,主要將B站的一些很好的學(xué)習(xí)視頻進(jìn)行總結(jié),或者是寫寫自己的開發(fā)心得,竟然最后寫了5w字,非常不可思議!感謝各位的閱讀(但是好像人也不多哈哈。不過無所謂啦,這個(gè)是今年的最后一篇文章。當(dāng)然,后續(xù)會繼續(xù)寫攔截器,以及OKHttp包裝之后的Retrofit,Retrofit真的是寫的非常出色! 還有對于阻塞隊(duì)列、并發(fā)的學(xué)習(xí)心得,不過目前還沒怎么學(xué)習(xí)?-?_?-

這篇文章主要的內(nèi)容:

  1. 分析OkHttpClient構(gòu)建Call(請求)的過程

  2. 分析構(gòu)建請求之后,OkHttp如果使用線程池API執(zhí)行請求。


首先通過常見的使用進(jìn)行源碼分析:

我們通常構(gòu)建一個(gè)OkHttpClient,通過OkHttp#newCall方法,結(jié)合我們構(gòu)建的Request對象,執(zhí)行execute(同步)或者enqueue(異步)進(jìn)行請求,所以我們首先看看OkHttp#newCall方法。

OkHttp#newCall

Call接口

RealCall是OkHttp定義的一個(gè)類,它繼承自Call。Call接口描述了一個(gè)準(zhǔn)備好被執(zhí)行的請求,我們可以通過Call的接口看看它能執(zhí)行那些操作:

OkHttp實(shí)現(xiàn)了這里的Factory,所以我們可以通過Factory工廠方法創(chuàng)建Call對象。

RealCall#newRealCall

此處的EventListener描述的是一個(gè)Http請求的過程。通常來說,一個(gè)Http請求,包括DNS解析(通過域名獲取IP地址),TCP/IP連接,然后再是應(yīng)用層的Http請求,我們可以通過這個(gè)類的方法略知一二。

所以再看完Call對象的構(gòu)建之后,我們來看另外兩個(gè)非常重要的方法,分別是enqueue和execute。

RealCall#enqueue

enqueue方法解釋了為什么OkHttp中的Call不能被執(zhí)行兩次。因?yàn)镃all也存在生命周期,已經(jīng)被執(zhí)行的話,還執(zhí)行一次會拋出異常。

此外,call被轉(zhuǎn)交給Dispatcher#enqueue方法。Dispatcher#enqueue方法會根據(jù)條件選擇是否立刻執(zhí)行當(dāng)前的call:

以上的條件主要是兩個(gè):

  1. 當(dāng)前正在執(zhí)行的Call的數(shù)量不能超過maxRequests(默認(rèn)定義為64)

  2. 對于同一個(gè)host的請你去不能超過maxRequestsPerHost(默認(rèn)定義為5)

跟蹤代碼發(fā)現(xiàn)這個(gè)host是使用HttpUrl類表示的,也就是我們常用的http url。

也就是說,如果滿足條件,就會將請求存儲到正在執(zhí)行的一步請求隊(duì)列(runningAsyncCalls),否則就存儲到就緒隊(duì)列(readyAsyncCalls)中:

readyAsyncCalls中的請求什么時(shí)候被執(zhí)行?

有一個(gè)非常明顯的事情,當(dāng)我們使用OkHttp執(zhí)行第一個(gè)請求的時(shí)候,這個(gè)請求肯定會被放入running隊(duì)列中,并且我們的Call對象其實(shí)還被包裝了一層AsyncCall:

我們的Call最后是要被送到線程池中執(zhí)行的,這意味著execute方法會被回調(diào),在execute方法中,攔截器幫助我們執(zhí)行了最重要的Http請求,并且返回給我們Repsonse。在finally塊里面,dispatcher調(diào)用了finish方法。當(dāng)一個(gè)請求完成的時(shí)候,勢必要去查看隊(duì)列中是否還有沒有被執(zhí)行的任務(wù):

Dispatcher可以通過promoteCalls,將ready隊(duì)列中的請求拉出來執(zhí)行:

這里會將ready隊(duì)列中的請求,在滿足不超過前面說的maxRequest和maxRequestPerHost的前提下取出來,放到running隊(duì)列中,然后交給executorService執(zhí)行。

彳亍,我們現(xiàn)在的目標(biāo)就是要研究一下線程池是如何執(zhí)行的我們的請求的。


使用線程池ExecutorService執(zhí)行call

Executor構(gòu)建了一個(gè)ThreadPoolExecutor作為線程池,一個(gè)線程池具有幾個(gè)比較重要的參數(shù):

  1. 核心線程數(shù),這里設(shè)定為0

  2. 最大線程數(shù),設(shè)定為max

  3. 線程存活時(shí)間。因?yàn)槲覀兌x在線程池中的線程的數(shù)量可能大于最大線程數(shù),如果大于最大線程數(shù),并且還是空閑狀態(tài)的這些線程,將會被移出。

  4. 任務(wù)隊(duì)列,任務(wù)隊(duì)列是一個(gè)BlockingQueue,它規(guī)定了任務(wù)的排隊(duì)方式。這里的實(shí)現(xiàn)是SynchronousQueue。

RealCall#execute

execute方法和enqueue方法類似,根據(jù)上述的分析可以很快弄明白:


為什么要使用SynchronousQueue?

為什么在OkHttp中非要使用SynchronousQueue作為阻塞隊(duì)列,使用fixed大小的隊(duì)列,比如ArrayBlockingQueue,LinkedBlockingQueue為啥不行捏?這一節(jié)主要想說明這個(gè)問題。

之所以要使用SynchronousQueue作為阻塞隊(duì)列,我們首先需要分析ThreadPoolExecutor執(zhí)行任務(wù)的特點(diǎn)。(關(guān)于不同的阻塞隊(duì)列的比較,我后面一定會寫文章來說明)。

在ThreadPoolExecutor#execute中,ctl這個(gè)量其實(shí)是workerCount和runState這兩個(gè)對于線程池【狀態(tài)】的一個(gè)組合:

ctl是一個(gè)AtomicInteger,它的32位被拆分成這樣兩個(gè)部分,前面三位用于表示這幾種狀態(tài):

workerCount表示線程池中的線程的數(shù)量,可以看到這個(gè)數(shù)量是非常龐大的,差不多有這么多:(2^29)-1 (about 500 million)。

ThreadPoolExecutor#execute

ok,稍微了解了ctl,我們來看看execute的代碼:

對于一個(gè)隨便的Runnable,我們首先會計(jì)算當(dāng)前workerCount和核心數(shù)量比較,如果小于核心數(shù)量,就使用addWorker創(chuàng)建新線程;否則,加入workerQueue。這里的workerQueue是我們指定的阻塞隊(duì)列。如果不能加入阻塞隊(duì)列(通常是因?yàn)槌^了固定的大小,比如ArrayBlockingQueue我們會指定大?。?,就會走到else if 分支,再調(diào)用addWorker創(chuàng)建新線程。 基于以上分析,我們上兩個(gè)例子。

Case#1 使用ArrayBlockingQueue,大小設(shè)置為1,其他的設(shè)置和OkHttp相同:

輸出結(jié)果:

并沒有輸出thread-2,如果按照上面的分析來看,因?yàn)閏ore大小為0,第一個(gè)if直接pass,然后在第二個(gè)分支中,現(xiàn)將任務(wù)加入workQueue,此時(shí)發(fā)現(xiàn)workerCount為0,立刻執(zhí)行任務(wù)1,workerQueue取出這個(gè)任務(wù);當(dāng)加入第二個(gè)任務(wù)時(shí),和上面類似,workerQueue存入任務(wù)2,但是workercount不是0,所以第二個(gè)任務(wù)就被擱置了。

Case#2,在加入一個(gè)任務(wù):

此時(shí),在加入一個(gè)任務(wù),在第三個(gè)任務(wù)進(jìn)來之前,和上面分析相同,出現(xiàn)第三個(gè)任務(wù),因?yàn)閣orkerQueue大小為1,存了第二個(gè)任務(wù),則第二個(gè)if沒有命中,會走下面的addWorker,創(chuàng)建一條新的線程,先執(zhí)行任務(wù)3,再執(zhí)行任務(wù)2,我們來看看輸出結(jié)果:

結(jié)果和我們分析的一樣,2 3 任務(wù)是在另外的線程池上執(zhí)行的。

通過這個(gè)現(xiàn)象,我們不難分析出使用ArrayBlockingQueue存在兩個(gè)顯著的局限性:

  1. 需要設(shè)置大小。如果前面的任務(wù)耗時(shí),后面的任務(wù)會存在隊(duì)列中,無法執(zhí)行

  2. 就算可以執(zhí)行了,執(zhí)行順序并非我們的預(yù)期。

基于以上兩點(diǎn)分析,我們就選擇了SynchronousQueue作為阻塞隊(duì)列。(但是為什么這個(gè)隊(duì)列可以保證順序,之后的文章會分析)。


所以!我們來小結(jié)一下本篇文章:

  1. ?我們通常使用的OkHttpClient是Call對象的一個(gè)工廠實(shí)現(xiàn)類,它通過newCall構(gòu)建Call對象。

  2. 構(gòu)建出的Call對象調(diào)用enqueue方法或者是execute方法時(shí),都會轉(zhuǎn)到Dispatcher的enqueue方法或者是execute方法中。

  3. Dispatcher內(nèi)部使用了ThreadPoolExecutor作為線程池。該線程池的阻塞隊(duì)列實(shí)現(xiàn)使用了SynchronousQueue,使用這個(gè)隊(duì)列是為了保證請求的順序和請求都被執(zhí)行。


DEVLOG 12.30 網(wǎng)絡(luò)請求框架(上)-- OkHttp中的Dispatcher的評論 (共 條)

分享到微博請遵守國家法律
大冶市| 吴江市| 资中县| 台中市| 米泉市| 绵阳市| 沂源县| 鸡西市| 友谊县| 镇平县| 自治县| 五家渠市| 南郑县| 河北区| 新干县| 日喀则市| 晴隆县| 巴东县| 九台市| 尼勒克县| 胶南市| 英山县| 涞水县| 定日县| 庆云县| 车致| 潮州市| 新民市| 竹山县| 金阳县| 杭州市| 西乌珠穆沁旗| 新昌县| 霍林郭勒市| 太保市| 河北区| 金湖县| 江阴市| 孟连| 垦利县| 上饶县|