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)鍵:
- while(true)這是一個(gè)死循環(huán)
- 第二個(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í)行。