Volley源碼閱讀——(一)為何適用于多而小

Volley算是比較簡(jiǎn)單的http庫(kù)枚驻,所以決定從Volley入手去開(kāi)始讀源碼之路快骗。
打算寫(xiě)一系列Volley源碼閱讀的文章首妖,順序按照我的源碼閱讀順序偎漫。這是第一章,為何適用于多而小有缆。
本章主要介紹我閱讀源碼的起始過(guò)程象踊,理解Volley的初始化工作和Volley中請(qǐng)求線程的工作原理。

1. 初步瀏覽 - 接口

接口 實(shí)現(xiàn)類 主要方法/作用
Cache NoCache
NetWork BasikNetWork performRequest()
MockNetWork
ResponseDelivery ExecutorDelivery postResponse()/postError()
RetryPolicy DefaultRetryPolicy
toolbox.HttpStack HttpClientStack 使用HttpClient來(lái)performRequest
HurlStack 使用HttpURLConnection來(lái)performRequest
MockHttpStack
toolbox.Authenticator DefaultAuthenticator

Volley源碼算是比較少的棚壁,我也沒(méi)有看源碼的經(jīng)驗(yàn)杯矩。于是先將接口列出,試圖知道設(shè)計(jì)人員一開(kāi)始都想做些啥袖外,這個(gè)方法卓有成效史隆。

接口列出如上,如何快速找到接口呢曼验?我用的Idea看的源碼泌射,里面非常清晰头镊,如下圖:



圖中1處綠色寫(xiě)著“I”就是Interface接口,甚至還有2處圓圈兩邊灰色的魄幕,打開(kāi)Request類會(huì)發(fā)現(xiàn)這是個(gè)抽象類相艇。
那下面把抽象類也列一下:

抽象類 實(shí)現(xiàn)類 主要方法/作用
Request ClearCacheRequest
ImageRequest
StringRequest
JsonRequest JsonArrayRequest
JsonObjectRequest

比較特殊的是JsonRequest也是Request的子類,同時(shí)它也是一個(gè)抽象類纯陨,再分別由JsonArrayRequest和JsonObjectRequest實(shí)現(xiàn)坛芽。

這些東西列出來(lái),可能就對(duì)框架已經(jīng)有了初步的了解翼抠,比如:

  • 這個(gè)框架可能做了緩存 —— 基于Cache接口
  • 用NetWork還是HttpStack來(lái)實(shí)現(xiàn)請(qǐng)求咙轩? —— 基于二者都有的performRequest()方法
  • 應(yīng)答有一個(gè)分發(fā)機(jī)制 —— 基于ResponseDelivery
  • 有重發(fā)機(jī)制 —— 基于RetryPolicy
  • 可能還有認(rèn)證機(jī)制 —— 基于Authenticator

這些東西先有個(gè)印象,有個(gè)印象能加快后面的進(jìn)度阴颖。

2. 簡(jiǎn)單使用

網(wǎng)上一搜一大把活喊,這里建議大家看博客的時(shí)候,找一些排版比較好的量愧。那些連代碼格式都沒(méi)有的就不要去看了钾菊。

參考Android Volley完全解析(一),初識(shí)Volley的基本用法偎肃,這個(gè)是我搜到的百度的第一個(gè)煞烫。

第三條 StringRequest介紹到:

(1). 初始化,創(chuàng)建RequestQueue對(duì)象

RequestQueue mQueue = Volley.newRequestQueue(context);  

(2). 創(chuàng)建StringRequest請(qǐng)求累颂,并add進(jìn)RequestQueue

StringRequest stringRequest = new StringRequest("http://www.baidu.com",  
                        new Response.Listener<String>() {  
                            @Override  
                            public void onResponse(String response) {  
                                Log.d("TAG", response);  
                            }  
                        }, new Response.ErrorListener() {  
                            @Override  
                            public void onErrorResponse(VolleyError error) {  
                                Log.e("TAG", error.getMessage(), error);  
                            }  
                        });  
mQueue.add(stringRequest);  

3. 參考簡(jiǎn)單使用理解初始化工作

看2中代碼滞详,初始化僅僅創(chuàng)建了一個(gè)RequestQueue,這里面完成了什么工作呢紊馏?

43行:初始化了默認(rèn)的緩存目錄
53-59行:根據(jù)不同的sdk版本創(chuàng)建不同的HttpStack(主要的實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求的對(duì)象)
63行:以HttpStack對(duì)象作為參數(shù)新建NetWork對(duì)象

猜想:這應(yīng)該是NetWork對(duì)象調(diào)用HttpStack對(duì)象實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求了

65行:以DiskBasedCache對(duì)象和netWork對(duì)象作為參數(shù)創(chuàng)建了RequestQueue對(duì)象
66行:RequestQueue執(zhí)行start方法

理一理思緒:

  • 簡(jiǎn)單說(shuō)就是創(chuàng)建了緩存對(duì)象DiskBasedCache和網(wǎng)絡(luò)請(qǐng)求對(duì)象BasicNetwork料饥,然后傳給RequestQueue的構(gòu)造器創(chuàng)建了RequestQueue對(duì)象。
  • BasicNetWork對(duì)象好像代理了HttpStack對(duì)象來(lái)執(zhí)行http請(qǐng)求的具體實(shí)現(xiàn)
  • 非常貼心地可以傳入HttpStack對(duì)象朱监,表示開(kāi)發(fā)人員可以自己選擇http請(qǐng)求的具體實(shí)現(xiàn)
  • RequestQueue對(duì)象執(zhí)行了start方法岸啡,不知道做了什么操作

現(xiàn)在來(lái)找一些比較關(guān)心的事情。

Q: 網(wǎng)絡(luò)請(qǐng)求到底怎么執(zhí)行的赌朋?NetWork和HttpStack到底是如何工作的凰狞?

Volley只是一個(gè)封裝庫(kù)篇裁,底層的http請(qǐng)求還是調(diào)用JDK接口沛慢。基于這樣的背景达布,去看一下NetWork和HttpStack的performRequest()方法团甲,這個(gè)方法一看起來(lái)就像是執(zhí)行http請(qǐng)求的。

BasicNetWork的performRequest()


發(fā)現(xiàn)關(guān)鍵的httpResponse還是由HttpStack實(shí)現(xiàn)的黍聂,所以只要看HttpStack躺苦。

HurlStack的performRequest()
ps. 由于14號(hào)字體無(wú)法截全方法身腻,不得不縮小字體

可以不去管其中的具體實(shí)現(xiàn),找到了其中比較重點(diǎn)的部分:responseStatus和response是依靠HttpURLConnection獲得的匹厘。我一開(kāi)始學(xué)習(xí)Android嘀趟,就是手寫(xiě)HttpURLConnection來(lái)實(shí)現(xiàn)get和post方法的。
反正response是HttpURLConnection實(shí)現(xiàn)的愈诚,暫時(shí)先不求甚解她按,假裝已經(jīng)拿到了response。

RequestQueue 構(gòu)造器做了什么工作炕柔?

StringRequest例子里面是兩個(gè)參數(shù)的RequestQueue構(gòu)造器

兩個(gè)參數(shù)的構(gòu)造器調(diào)用了3個(gè)參數(shù)的構(gòu)造器方法酌泰,并將第三個(gè)參數(shù)設(shè)置為默認(rèn)4∝袄郏看注釋我們就知道陵刹,這個(gè)4是4個(gè)用來(lái)執(zhí)行請(qǐng)求的線程,大膽猜想欢嘿,RequestQueue中有一個(gè)線程池衰琐,默認(rèn)線程數(shù)量是4。
看3個(gè)參數(shù)的構(gòu)造器


第三個(gè)參數(shù)確實(shí)命名為threadPoolSize炼蹦。同時(shí)加上了第4個(gè)參數(shù)默認(rèn)值碘耳,一個(gè)ResponseDelivery的唯一實(shí)現(xiàn)ExecutorDelivery的對(duì)象。這個(gè)類我們?cè)谝婚_(kāi)始整理接口的時(shí)候就見(jiàn)過(guò)框弛,暫時(shí)只需要知道用來(lái)分發(fā)response辛辨。

再看最終的4個(gè)參數(shù)的構(gòu)造器


原來(lái)只是賦值給成員變量而已。而且心心念念的線程池也僅僅是一個(gè)線程對(duì)象數(shù)組瑟枫。
很好斗搞,其實(shí)可以發(fā)現(xiàn),一開(kāi)始整理出來(lái)的接口慷妙,有3個(gè)已經(jīng)被RequestQueue成員變量實(shí)現(xiàn)了:Cache僻焚、NetWork、和ResponseDelivery

queue.start();完成了什么工作膝擂?

start之前都是對(duì)象的創(chuàng)建虑啤,到這一步,終于要運(yùn)行起來(lái)了吧架馋!


142行:備注已經(jīng)說(shuō)明了狞山,確保關(guān)閉dispatcher線程。具體邏輯肯定和start相反叉寂,那么就先不care吧
144-145行:這里居然偷偷創(chuàng)建了一個(gè)緩存分發(fā)器萍启,進(jìn)入實(shí)現(xiàn)會(huì)發(fā)現(xiàn)這繼承了線程。
148-152行:這里估計(jì)就是關(guān)鍵了,for循環(huán)一個(gè)一個(gè)地創(chuàng)建NetworkDispatcher對(duì)象將線程池?cái)?shù)組填滿勘纯,然后再將線程運(yùn)行起來(lái)局服。

看到這里想必和我一樣不耐煩了,這個(gè)dispatcher是個(gè)什么東西驳遵,線程現(xiàn)在就運(yùn)行起來(lái)淫奔,豈不是占資源,而且堤结,怎么保證線程一直在等用戶發(fā)請(qǐng)求搏讶?

所以還是先看一看,networkDispatcher.start();之后會(huì)有什么發(fā)生霍殴?
這既然是繼承了線程媒惕,那直接找到run()方法,好在一開(kāi)始就看到了關(guān)鍵:

  1. while(true)這是一個(gè)死循環(huán)
  2. 第二個(gè)紅框里面應(yīng)該就是從隊(duì)列里面拿請(qǐng)求了来庭。所以不斷從隊(duì)列里面拿請(qǐng)求妒蔚,然后執(zhí)行。

這個(gè)mQueue是什么對(duì)象月弛?隊(duì)列里面要是沒(méi)有請(qǐng)求存在又怎么辦肴盏?
再看start方法中NetworkDispatcher對(duì)象的構(gòu)造器參數(shù)


這個(gè)mNetworkQueue是一個(gè)優(yōu)先級(jí)阻塞隊(duì)列,保存的是Request對(duì)象


NetworkDispatcher中的mQueue確實(shí)就是這個(gè)mNetworkQueue

那么不難猜想帽衙,mQueue(優(yōu)先級(jí)阻塞隊(duì)列PriorityBlockingQueue)在為空的時(shí)候take會(huì)阻塞線程稍味,直到queue不為空怒见。那么進(jìn)入take()方法去驗(yàn)證一下這個(gè)猜想:
直接點(diǎn)擊take方法會(huì)跳入BlockingQueue接口里面的方法,如下

點(diǎn)擊左邊那個(gè)向下箭頭就能看到進(jìn)入實(shí)現(xiàn),我們選擇PriorityBlockingQueue:


我們看到關(guān)鍵的一行沧卢,dequeue()方法肯定就是隊(duì)列彈出一個(gè)對(duì)象(可以自己去看看具體實(shí)現(xiàn))削解,為空的話蜓耻,就會(huì)await()存哲。
其中notEmpty對(duì)象是一個(gè)條件對(duì)象,這設(shè)計(jì)了多線程的鎖翩剪。


在PriorityBlockingQueue的構(gòu)造器內(nèi)初始化


這里面的原理需要再開(kāi)一篇來(lái)細(xì)講乳怎。這里我們只需要知道阻塞隊(duì)列為空,當(dāng)前線程就會(huì)被阻塞await()前弯。如何喚醒線程蚪缀,后文講一個(gè)請(qǐng)求的發(fā)送流程繼續(xù)講。

但是默認(rèn)有4個(gè)線程恕出,4個(gè)線程都會(huì)被阻塞嗎询枚?

我們不妨進(jìn)入await()方法里面去再深入看一看:
這里我們需要換一種尋找實(shí)現(xiàn)方法的方式,找到notEmpty的初始化



進(jìn)入newCondition()方法


再找到sync對(duì)象的聲明地方


L旮Aぁ!這是可重入鎖狈醉!而且下面就是Sync抽象類的聲明廉油,找到其中newCondition()方法


發(fā)現(xiàn)返回的是一個(gè)ConditionObject對(duì)象,找到這個(gè)類的聲明并找到其中的await()方法:


2031行:將當(dāng)前線程加入條件等待隊(duì)列
2034行:檢查當(dāng)前線程node是否還在等待條件苗傅,需要等就進(jìn)入循環(huán)執(zhí)行LovkSupport.Park()來(lái)阻塞線程抒线。
看一看這部分源碼,可以理解渣慕,線程來(lái)一個(gè)阻塞一個(gè)嘶炭,就是一個(gè)阻塞隊(duì)列。

初始化工作也是為了一般工作能夠正常進(jìn)行逊桦,這里也保留一個(gè)印象眨猎,便于后面具體工作展開(kāi)的理解。

4. 參考簡(jiǎn)單使用理解請(qǐng)求如何發(fā)送

StringRequest stringRequest = new StringRequest("http://www.baidu.com",  
                        new Response.Listener<String>() {  
                            @Override  
                            public void onResponse(String response) {  
                                Log.d("TAG", response);  
                            }  
                        }, new Response.ErrorListener() {  
                            @Override  
                            public void onErrorResponse(VolleyError error) {  
                                Log.e("TAG", error.getMessage(), error);  
                            }  
                        });  
mQueue.add(stringRequest);  

再看一看基本使用的代碼强经,我已經(jīng)寫(xiě)累了睡陪,太長(zhǎng)了。太長(zhǎng)不想看匿情,我也不想寫(xiě)了兰迫。看炬称,里面requestQueue.add(request)這樣就把請(qǐng)求發(fā)送出去了汁果。前面我們知道線程被阻塞住了。那當(dāng)有請(qǐng)求add進(jìn)來(lái)的時(shí)候玲躯,是如何喚醒線程的呢据德?

找到RequestQueue類中的add方法:


簡(jiǎn)單分析一下:
229:讓request對(duì)象持有當(dāng)前RequestQueue對(duì)象的引用
236:addMarker,注意request執(zhí)行到每一個(gè)步驟都會(huì)設(shè)置一個(gè)marker跷车,用來(lái)標(biāo)志請(qǐng)求進(jìn)行的階段
239:是否需要緩存的條件語(yǔ)句晋控,當(dāng)然先從不需要的看起
240:mNetworkQueue.add,這個(gè)就是關(guān)鍵姓赤。
那240這里add進(jìn)去了赡译,怎么喚醒線程去執(zhí)行呢?看源碼


看到最后一個(gè)signal方法不铆,這就是和await相對(duì)應(yīng)的喚醒方法蝌焚。假如還是不太理解這個(gè)阻塞和喚醒到底怎么回事,建議和我一樣誓斥,寫(xiě)一個(gè)小demo看看

打印出來(lái)的效果是


不會(huì)無(wú)限打印print只洒,也不會(huì)打印print finally

結(jié)合初始化那部分,就完整地呈現(xiàn)了多線程如何去實(shí)時(shí)發(fā)送新增的請(qǐng)求的流程劳坑。

結(jié)論
這也解釋了為何Volley有利于多而小的請(qǐng)求毕谴,畢竟有4個(gè)線程可以使用。反而大的請(qǐng)求其實(shí)并沒(méi)有做什么優(yōu)化,用Volley執(zhí)行大請(qǐng)求并沒(méi)有什么優(yōu)勢(shì)涝开,反而可能因?yàn)樘臅r(shí)而占用了線程循帐,導(dǎo)致其他小的請(qǐng)求無(wú)法及時(shí)執(zhí)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末舀武,一起剝皮案震驚了整個(gè)濱河市拄养,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌银舱,老刑警劉巖瘪匿,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異寻馏,居然都是意外死亡棋弥,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)诚欠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)顽染,“玉大人,你說(shuō)我怎么就攤上這事聂薪〖页耍” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵藏澳,是天一觀的道長(zhǎng)仁锯。 經(jīng)常有香客問(wèn)我,道長(zhǎng)翔悠,這世上最難降的妖魔是什么业崖? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮蓄愁,結(jié)果婚禮上双炕,老公的妹妹穿的比我還像新娘。我一直安慰自己撮抓,他們只是感情好妇斤,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著丹拯,像睡著了一般站超。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乖酬,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天死相,我揣著相機(jī)與錄音,去河邊找鬼咬像。 笑死算撮,一個(gè)胖子當(dāng)著我的面吹牛生宛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肮柜,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼陷舅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了素挽?” 一聲冷哼從身側(cè)響起蔑赘,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤狸驳,失蹤者是張志新(化名)和其女友劉穎预明,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體耙箍,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撰糠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辩昆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阅酪。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖汁针,靈堂內(nèi)的尸體忽然破棺而出术辐,到底是詐尸還是另有隱情,我是刑警寧澤施无,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布辉词,位于F島的核電站,受9級(jí)特大地震影響猾骡,放射性物質(zhì)發(fā)生泄漏瑞躺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一兴想、第九天 我趴在偏房一處隱蔽的房頂上張望幢哨。 院中可真熱鬧,春花似錦嫂便、人聲如沸捞镰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)岸售。三九已至,卻和暖如春蔚龙,著一層夾襖步出監(jiān)牢的瞬間冰评,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工木羹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留甲雅,地道東北人解孙。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像抛人,于是被迫代替她去往敵國(guó)和親弛姜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

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