編者按: 本博文主要記錄我對Volley的理解以及從網(wǎng)上查閱的一些資料屿聋,主要用作一個學(xué)習(xí)筆記如绸,若是有讀者發(fā)現(xiàn)其中有理解錯誤之處赘来,還望評論區(qū)幫忙指出,謝謝
Q & A:
Q: Volley為什么在2.3以前使用HttpClientStack呛梆,而在2.3以后使用HurlStack锐涯?
A: 一般來說,HttpURLConnection相對輕量級填物,也比較小纹腌,而Apache HTTP Client接口多,比較大融痛,所以一般情況下HttpURLConnection是最佳選擇壶笼。然而在 Froyo(2.2) 之前,HttpURLConnection 有個重大 Bug雁刷,調(diào)用 close() 函數(shù)會影響連接池,導(dǎo)致連接復(fù)用失效保礼,所以在 Froyo 之前使用 HttpURLConnection 需要關(guān)閉 keepAlive沛励。另外在 2.3 HttpURLConnection 默認(rèn)開啟了 gzip 壓縮,提高了 HTTPS 的性能炮障, 4.0 HttpURLConnection 支持了請求結(jié)果緩存目派。所以綜上所述,對 Android 來說胁赢,在 2.3 之后建議使用 HttpURLConnection企蹭,之前建議使用 AndroidHttpClient。Q: Volley在使用HttpClient進(jìn)行網(wǎng)絡(luò)請求時會設(shè)置User-Agent字段,而使用HttpURLConnection時則不會谅摄,原因是什么徒河?
A: 使用Fiddler/Charles抓包會發(fā)現(xiàn),HttpURLConnection 默認(rèn)是有 User-Agent 的送漠,實際在請求發(fā)出之前顽照,會檢測 User-Agent 是否為空,如果為空闽寡,則加上系統(tǒng)默認(rèn) User-Agent代兵。在 Android 2.1 之后,我們可以通過String userAgent = System.getProperty("http.agent")得到系統(tǒng)默認(rèn)的 User-Agent爷狈。Volley 如果希望自定義 User-Agent植影,可在自定義 Request 中重寫 getHeaders() 函數(shù)。
在使用HttpClient進(jìn)行網(wǎng)絡(luò)請求時涎永,Volley會將請求頭中的 User-Agent 字段設(shè)置為 App 的 ${packageName}/${versionCode}何乎,如果異常則使用 "volley/0"(這個獲取 User-Agent 的操作應(yīng)該放到 if else 內(nèi)部更合適)。Q: Volley中為什么使用ByteArrayPool進(jìn)行存儲網(wǎng)絡(luò)數(shù)據(jù)土辩?
A: 首先可以試想一下ByteArrayPool產(chǎn)生的背景支救。當(dāng)網(wǎng)絡(luò)請求得到返回數(shù)據(jù)以后,我們需要在內(nèi)存中開辟出一塊區(qū)域來存放我們得到的網(wǎng)絡(luò)數(shù)據(jù)拷淘,不論是json還是圖片各墨,都會存在于內(nèi)存的某一塊區(qū)域,然后拿到UI顯示启涯,然而移動客戶端請求一般都是相當(dāng)頻繁的操作贬堵,想一下我們平時使用今日頭條等一些客戶端,幾乎每一個操作都要進(jìn)行網(wǎng)絡(luò)請求结洼。那么問題來了:這么頻繁的數(shù)據(jù)請求黎做,獲得數(shù)據(jù)以后我們先要在堆內(nèi)存開辟存儲空間,然后顯示松忍,等到時機(jī)成熟蒸殿,GC再回收這塊區(qū)域,如此往復(fù)鸣峭,那么GC的負(fù)擔(dān)就會相當(dāng)?shù)闹睾晁欢鳤ndroid客戶端處理能力有限,頻繁GC對客戶端的性能是會產(chǎn)生很大的影響的摊溶。那么我們是否能夠通過某種機(jī)制來減少內(nèi)存分配次數(shù)和GC次數(shù)爬骤,從而達(dá)到提高程序性能的目的呢?我猜想這個問題便是這個類誕生的原因了莫换。
其次就需要看這個類是如何實現(xiàn)這樣的目的的呢霞玄?從類名可以看出骤铃,這是一個字節(jié)數(shù)組緩存池,用來緩存從網(wǎng)絡(luò)請求中獲得的數(shù)據(jù)的坷剧。ByteArrayPool利用getBuf和returnBuf以及mBuffersByLastUse和mBuffersBySize來完成字節(jié)數(shù)組的緩存惰爬。當(dāng)需要使內(nèi)存區(qū)域的時候,先從已經(jīng)分配的緩存區(qū)域中獲得以減少內(nèi)存分配次數(shù)听隐。當(dāng)空間用完以后补鼻,再將數(shù)據(jù)返回到此緩沖區(qū)。這樣雅任,就可以減少內(nèi)存區(qū)域堆內(nèi)存的波動和減少GC的回收风范,讓CPU把更多的性能留給頁面的渲染,提高性能沪么。通過這個類發(fā)現(xiàn)硼婿,谷歌對技術(shù)的細(xì)節(jié)十分考究。Q: Volley為什么不適合用來下載大的數(shù)據(jù)文件禽车?
A: 因為Volley會在解析的過程中將所有的響應(yīng)數(shù)據(jù)保留在內(nèi)存中寇漫。對于下載大量數(shù)據(jù)的操作,請考慮使用DownloadManager殉摔。Q: 4. Volley的DiskBasedCache類會把服務(wù)器相應(yīng)信息寫入磁盤州胳,然后再讀磁盤取
出緩存,writeInt()逸月,writeLong()方法為什么要進(jìn)行位運(yùn)算栓撞?
A: Java的IO本來就是對byte的操作,一個int占4個byte碗硬,所以需要按位寫入瓤湘。因為網(wǎng)絡(luò)字節(jié)序是大端字節(jié)序,而在80X86平臺中恩尾,是以小端法存放的弛说,比如我們經(jīng)過網(wǎng)絡(luò)發(fā)送0x12345678這個整形,但實際上流是0x87654321翰意。因為不同的平臺int long等木人,他們的字節(jié)存儲的順序可能是不一樣的×晕铮可能低位在前或者高位在前虎囚。writeInt() 和 readInt()以字節(jié)為單位,用一致的順序讀寫蔫磨,就能適配不同的平臺。Q: RetryPolicy 如何做到重試的呢圃伶?具體流程是什么樣的呢 堤如?
A: 首先假設(shè)你設(shè)置了重試的策略蒲列,其次performRequest外面其實是個while 循環(huán)。假設(shè)在網(wǎng)絡(luò)請求過程中產(chǎn)生異常搀罢, 比如read time out蝗岖, catch 這個異常的代碼會看看是否重試,如果是重試榔至,就把這個異常吞掉抵赢,然后繼續(xù)下一次循環(huán),否則唧取,拋出異常铅鲤,由上一層代碼去處理。Q: 當(dāng)UI線程不斷的向隊列添加請求枫弟,隊列如果有大小限制邢享,隊列滿的時候不會ui線程阻塞么?如果沒有大小限制淡诗,不會導(dǎo)致oom么骇塘?
A: PriorityBlockingQueue:一個無界阻塞隊列,PriorityBlockingQueue的默認(rèn)容量是11韩容;它使用與類 PriorityQueue 相同的順序規(guī)則款违,并且提供了阻塞獲取操作。雖然此隊列邏輯上是無界的群凶,但是資源被耗盡時試圖執(zhí)行 add 操作也將失敳宓(導(dǎo)致 OutOfMemoryError)。此類不允許使用 null 元素座掘。依賴自然順序的優(yōu)先級隊列也不允許插入不可比較的對象(這樣做會導(dǎo)致拋出 ClassCastException)递惋。第一個問題:不會阻塞,只有獲取的時候(如果隊列為空溢陪,則會阻塞)萍虽;第二個問題:會導(dǎo)致OOM。首先形真,volley里面用的BlockingQueue是PriorityBlockingQueue的實現(xiàn)類(隊列定長11)杉编,這個類有個特點:不會阻塞調(diào)用者線程,所以UI線程不會被阻塞咆霜,這是第一個問題邓馒;第二個問題,有可能會導(dǎo)致堆內(nèi)存溢出蛾坯,所以使用這個實現(xiàn)類光酣,還有個要求:調(diào)用者的生產(chǎn)速度不能快于處理者的處理速度。Q: CacheDispatcher和NetworkDispatcher兩條線程都處理同一個Cache脉课,難道不會出現(xiàn)互斥現(xiàn)象嗎救军?為什么不加鎖财异?
A: 因為里邊的方法都是同步的。Q: Volley實現(xiàn)網(wǎng)絡(luò)緩存的機(jī)制唱遭?
A: Volley的緩存方法:根據(jù)進(jìn)行請求時服務(wù)器返回的緩存控制Header對請求結(jié)果進(jìn)行緩存戳寸,下次請求時判斷如果沒有過期就直接使用緩存加快響應(yīng)速度,如果需要會再次請求服務(wù)器進(jìn)行刷新拷泽,如果服務(wù)器返回了304疫鹊,表示請求的資源自上次請求緩存后還沒有改變,這種情況就直接用緩存不用再次刷新頁面司致,不過這要服務(wù)器支持了拆吆。當(dāng)對上次的請求進(jìn)行緩存后,在下次請求時即使沒有網(wǎng)絡(luò)也可以請求成功蚌吸,關(guān)鍵的是锈拨,緩存的處理對用戶完全是透明的,對于一些簡單的情況會省去緩存相關(guān)的一些事情羹唠。Q: Volley中的設(shè)計模式
A:
策略模式
含義:定義一系列的算法奕枢,把它們一個個封裝起來,并且使他們可互相替換佩微。本模式使得算法可獨立于使用它的客戶而變化缝彬。
適用場景:一個類定義了多種行為,并且這些行為在這個類的方法中以多個條件語句的形式出現(xiàn)哺眯,那么可以使用策略模式避免在類中使用大量的條件語句谷浅。
Volley中的策略模式:在Volley中對于HttpStack的設(shè)計用到的就是策略模式。我們知道Android Framework里面同時包含HttpURLConnection和Apache HTTP Client 2套Http框架奶卓,HttpURLConnection相對輕量級一疯,也比較小,而Apache HTTP Client接口多夺姑,比較大墩邀,HttpURLConnection是最佳選擇,但在Android SDK小于9時盏浙,HttpURLConnection存在一些bug眉睹,所以當(dāng)Android SDK小于9時,基于HttpClient創(chuàng)建HttpStack废膘,否則基于HttpURLConnection創(chuàng)建HttpStack竹海。所以Volley通過策略模式,在SDK不同的版本時選用不同的策略丐黄,并且該策略也可以被替換斋配,而不需要修改Volley類的代碼。模板方法模式
含義:定義一個操作中算法的骨架,而將一些步驟延遲到子類中许起。模板方法使子類可以不改變一個算法結(jié)構(gòu)即可重定義該算法的某些特定步驟十偶。
適用場景:設(shè)計者需要給出一個算法的固定步驟菩鲜,并將某些步驟的具體實現(xiàn)留給子類來實現(xiàn)园细;需要對代碼進(jìn)行重構(gòu),將各個子類公共行為抽取出來集中到一個共同的父類中以避免代碼重復(fù)接校。
Volley中的模板方法模式:Volley中對于Request的設(shè)計用到的就是模板方法模式猛频。無論是請求Image,String蛛勉,JsonObject還是JsonArray鹿寻,唯一的區(qū)別就是對返回數(shù)據(jù)的解析方式(parseNetworkError)不同,如果我們就可以通過模板方法模式對解析方式進(jìn)行抽象诽凌,讓子類分別實現(xiàn)毡熏,這樣如果有新的對象返回需要解析,只要新增子類實現(xiàn)對返回數(shù)據(jù)的解析方式就可以實現(xiàn)功能拓展侣诵。
-
Q: Volley的優(yōu)點
A:
- 默認(rèn)采用緩存機(jī)制痢法,只有當(dāng)緩存中不存在相應(yīng)的請求數(shù)據(jù)時才進(jìn)行網(wǎng)絡(luò)請求,這樣減少了應(yīng)用響應(yīng)時間杜顺,提高了程序性能财搁,非常適合于移動設(shè)備上面的網(wǎng)絡(luò)請求,因為移動設(shè)備上的網(wǎng)絡(luò)請求的特點是單次請求的數(shù)據(jù)量比較小躬络,但數(shù)據(jù)請求操作比較頻繁尖奔,Volley便是為此類網(wǎng)絡(luò)請求而生;并且默認(rèn)的緩存實現(xiàn)穷当,將緩存以文件的形式存儲在 Disk提茁,程序退出后不會丟失;
- 對緩存文件進(jìn)行雙重校驗馁菜,先校驗是否過期茴扁,未過期的前提下,校驗是否新鮮火邓,這樣既能減少應(yīng)用程序響應(yīng)時間丹弱,又能確保用戶能獲得最新數(shù)據(jù);(是否過期校驗可以確保用戶獲得是數(shù)據(jù)是有效數(shù)據(jù)铲咨;先將未過期數(shù)據(jù)發(fā)送給用戶躲胳,可以減少響應(yīng)時間;然后校驗數(shù)據(jù)是否新鮮纤勒,如果不新鮮坯苹,則在下次請求數(shù)據(jù)時返回最新數(shù)據(jù),這很符合用戶使用習(xí)慣摇天。一般進(jìn)入一個應(yīng)用程序后并不一定需要立即刷新數(shù)據(jù)粹湃,而是等到用戶刷新時恐仑,再提供新數(shù)據(jù),這樣可以減少應(yīng)用啟動時間为鳄,減少不必要的流量消耗)裳仆;
- 根據(jù)Android API而默認(rèn)使用不同的網(wǎng)絡(luò)請求方案,API 9以前的應(yīng)用使用HttpClient孤钦,API 9及以后的設(shè)備使用HttpUrlConnection歧斟,這樣既能夠避免HttpUrlConnection在API 9以前的弊端(調(diào)用close方法會影響連接池,導(dǎo)致連接復(fù)用失效)偏形,又能夠最大限度地提高應(yīng)用程序性能静袖;
- ByteArrayPool利用getBuf和returnBuf以及mBuffersByLastUse和mBuffersBySize完成字節(jié)數(shù)組的緩存。當(dāng)需要使內(nèi)存區(qū)域的時候俊扭,先從已經(jīng)分配的區(qū)域中獲得以減少內(nèi)存分配次數(shù)队橙。當(dāng)空間用完以后,在將數(shù)據(jù)返回給此緩沖區(qū)萨惑。這樣捐康,就會減少內(nèi)存區(qū)域堆內(nèi)存的波動和減少GC的回收,讓CPU把更多的性能留給頁面的渲染咒钟,提高性能吹由。通過這個類發(fā)現(xiàn),谷歌對技術(shù)的細(xì)節(jié)十分考究朱嘴;
- 谷歌官方提供的網(wǎng)絡(luò)框架倾鲫,基本不需要考慮兼容性問題;
- 框架比較輕量級萍嬉,減少程序內(nèi)存占用(程序性能的另一方面)乌昔;
- 架構(gòu)設(shè)計比較優(yōu)雅,面向接口編程壤追,極大地提高了框架的可擴(kuò)展性磕道,編程人員可以根據(jù)自己應(yīng)用的特點定制自己的網(wǎng)絡(luò)請求、響應(yīng)頭行冰、響應(yīng)數(shù)據(jù)等溺蕉;
- 官方文檔詳細(xì),網(wǎng)絡(luò)資料也很豐富悼做,源碼書寫優(yōu)雅疯特,規(guī)范,編程人員學(xué)習(xí)成本低肛走。