1. 功能介紹
1.1. Volley
Volley 是 Google 推出的 Android 異步網(wǎng)絡(luò)請求框架和圖片加載框架传睹。在 Google I/O 2013 大會上發(fā)布。
名字由來:a burst or emission of many things or a large amount at once
發(fā)布演講時(shí)候的配圖
從名字由來和配圖中無數(shù)急促的火箭可以看出 Volley 的特點(diǎn):特別適合數(shù)據(jù)量小沟绪,通信頻繁的網(wǎng)絡(luò)操作怔檩。(個人認(rèn)為 Android 應(yīng)用中絕大多數(shù)的網(wǎng)絡(luò)操作都屬于這種類型)褪秀。
1.2 Volley 的主要特點(diǎn)
(1). 擴(kuò)展性強(qiáng)。Volley 中大多是基于接口的設(shè)計(jì)珠洗,可配置性強(qiáng)溜歪。
(2). 一定程度符合 Http 規(guī)范若专,包括返回 ResponseCode(2xx许蓖、3xx、4xx调衰、5xx)的處理膊爪,請求頭的處理,緩存機(jī)制的支持等嚎莉。并支持重試及優(yōu)先級定義米酬。
(3). 默認(rèn) Android2.3 及以上基于 HttpURLConnection,2.3 以下基于 HttpClient 實(shí)現(xiàn)趋箩,這兩者的區(qū)別及優(yōu)劣在4.2.1 Volley中具體介紹赃额。
(4). 提供簡便的圖片加載工具加派。
2. 總體設(shè)計(jì)
2.1 總體設(shè)計(jì)圖
上面是 Volley 的總體設(shè)計(jì)圖,主要是通過兩種Dispatch Thread不斷從RequestQueue中取出請求跳芳,根據(jù)是否已緩存調(diào)用Cache或Network這兩類數(shù)據(jù)獲取接口之一芍锦,從內(nèi)存緩存或是服務(wù)器取得請求的數(shù)據(jù),然后交由ResponseDelivery去做結(jié)果分發(fā)及回調(diào)處理飞盆。
2.2 Volley 中的概念
簡單介紹一些概念娄琉,在詳細(xì)設(shè)計(jì)中會仔細(xì)介紹。
Volley 的調(diào)用比較簡單吓歇,通過 newRequestQueue(…) 函數(shù)新建并啟動一個請求隊(duì)列RequestQueue后孽水,只需要往這個RequestQueue不斷 add Request 即可。
Volley:Volley 對外暴露的 API城看,通過 newRequestQueue(…) 函數(shù)新建并啟動一個請求隊(duì)列RequestQueue女气。
Request:表示一個請求的抽象類。StringRequest测柠、JsonRequest主卫、ImageRequest都是它的子類,表示某種類型的請求鹃愤。
RequestQueue:表示請求隊(duì)列簇搅,里面包含一個CacheDispatcher(用于處理走緩存請求的調(diào)度線程)、NetworkDispatcher數(shù)組(用于處理走網(wǎng)絡(luò)請求的調(diào)度線程)软吐,一個ResponseDelivery(返回結(jié)果分發(fā)接口)瘩将,通過 start() 函數(shù)啟動時(shí)會啟動CacheDispatcher和NetworkDispatchers。
CacheDispatcher:一個線程凹耙,用于調(diào)度處理走緩存的請求姿现。啟動后會不斷從緩存請求隊(duì)列中取請求處理,隊(duì)列為空則等待肖抱,請求處理結(jié)束則將結(jié)果傳遞給ResponseDelivery去執(zhí)行后續(xù)處理备典。當(dāng)結(jié)果未緩存過、緩存失效或緩存需要刷新的情況下意述,該請求都需要重新進(jìn)入NetworkDispatcher去調(diào)度處理提佣。
NetworkDispatcher:一個線程,用于調(diào)度處理走網(wǎng)絡(luò)的請求荤崇。啟動后會不斷從網(wǎng)絡(luò)請求隊(duì)列中取請求處理拌屏,隊(duì)列為空則等待,請求處理結(jié)束則將結(jié)果傳遞給ResponseDelivery去執(zhí)行后續(xù)處理术荤,并判斷結(jié)果是否要進(jìn)行緩存倚喂。
ResponseDelivery:返回結(jié)果分發(fā)接口,目前只有基于ExecutorDelivery的在入?yún)?handler 對應(yīng)線程內(nèi)進(jìn)行分發(fā)瓣戚。
HttpStack:處理 Http 請求端圈,返回請求結(jié)果焦读。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack。
Network:調(diào)用HttpStack處理請求舱权,并將結(jié)果轉(zhuǎn)換為可被ResponseDelivery處理的NetworkResponse吨灭。
Cache:緩存請求結(jié)果,Volley 默認(rèn)使用的是基于 sdcard 的DiskBasedCache刑巧。NetworkDispatcher得到請求結(jié)果后判斷是否需要存儲在 Cache喧兄,CacheDispatcher會從 Cache 中取緩存結(jié)果。
3. 流程圖
Volley 請求流程圖
上圖是 Volley 請求時(shí)的流程圖啊楚,在 Volley 的發(fā)布演講中給出吠冤,我在這里將其用中文重新畫出。
4. 詳細(xì)設(shè)計(jì)
4.1 類關(guān)系圖
這是 Volley 框架的主要類關(guān)系圖
圖中紅色圈內(nèi)的部分恭理,組成了 Volley 框架的核心拯辙,圍繞 RequestQueue 類,將各個功能點(diǎn)以組合的方式結(jié)合在了一起颜价。各個功能點(diǎn)也都是以接口或者抽象類的形式提供涯保。
紅色圈外面的部分,在 Volley 源碼中放在了 toolbox 包中周伦,作為 Volley 為各個功能點(diǎn)提供的默認(rèn)的具體實(shí)現(xiàn)夕春。
通過類圖我們看出, Volley 有著非常好的拓展性专挪。通過各個功能點(diǎn)的接口及志,我們可以給出自定義的,更符合我們需求的具體實(shí)現(xiàn)寨腔。
多用組合速侈,少用繼承;針對接口編程迫卢,不針對具體實(shí)現(xiàn)編程倚搬。
優(yōu)秀框架的設(shè)計(jì),令人叫絕乾蛤,受益良多每界。
4.2 核心類功能介紹
4.2.1 Volley.java
這個和 Volley 框架同名的類,其實(shí)是個工具類幻捏,作用是構(gòu)建一個可用于添加網(wǎng)絡(luò)請求的RequestQueue對象盆犁。
(1). 主要函數(shù)
Volley.java 有兩個重載的靜態(tài)方法。
public static RequestQueue newRequestQueue(Context context)
public static RequestQueue newRequestQueue(Context context, HttpStack stack)
第一個方法的實(shí)現(xiàn)調(diào)用了第二個方法篡九,傳 HttpStack 參數(shù)為 null。
第二個方法中醋奠,如果 HttpStatck 參數(shù)為 null榛臼,則如果系統(tǒng)在 Gingerbread 及之后(即 API Level >= 9)伊佃,采用基于 HttpURLConnection 的 HurlStack,如果小于 9沛善,采用基于 HttpClient 的 HttpClientStack航揉。
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
得到了 HttpStack,然后通過它構(gòu)造一個代表網(wǎng)絡(luò)(Network)的具體實(shí)現(xiàn)BasicNetwork。
接著構(gòu)造一個代表緩存(Cache)的基于 Disk 的具體實(shí)現(xiàn)DiskBasedCache金刁。
最后將網(wǎng)絡(luò)(Network)對象和緩存(Cache)對象傳入構(gòu)建一個 RequestQueue帅涂,啟動這個 RequestQueue,并返回尤蛮。
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
我們平時(shí)大多采用Volly.newRequestQueue(context)的默認(rèn)實(shí)現(xiàn)媳友,構(gòu)建 RequestQueue。
通過源碼可以看出产捞,我們可以拋開 Volley 工具類構(gòu)建自定義的 RequestQueue醇锚,采用自定義的HttpStatck,采用自定義的Network實(shí)現(xiàn)坯临,采用自定義的 Cache 實(shí)現(xiàn)等來構(gòu)建RequestQueue焊唬。
優(yōu)秀框架的高可拓展性的魅力來源于此啊
(2). HttpURLConnection 和 AndroidHttpClient(HttpClient 的封裝)如何選擇及原因:
在 Froyo(2.2) 之前,HttpURLConnection 有個重大 Bug看靠,調(diào)用 close() 函數(shù)會影響連接池赶促,導(dǎo)致連接復(fù)用失效,所以在 Froyo 之前使用 HttpURLConnection 需要關(guān)閉 keepAlive挟炬。
另外在 Gingerbread(2.3) HttpURLConnection 默認(rèn)開啟了 gzip 壓縮芳杏,提高了 HTTPS 的性能,Ice Cream Sandwich(4.0) HttpURLConnection 支持了請求結(jié)果緩存辟宗。
再加上 HttpURLConnection 本身 API 相對簡單爵赵,所以對 Android 來說,在 2.3 之后建議使用 HttpURLConnection泊脐,之前建議使用 AndroidHttpClient空幻。
(3). 關(guān)于 User Agent
通過代碼我們發(fā)現(xiàn)如果是使用 AndroidHttpClient,Volley 還會將請求頭中的 User-Agent 字段設(shè)置為 App 的 ${packageName}/${versionCode}容客,如果異常則使用 "volley/0"秕铛,不過這個獲取 User-Agent 的操作應(yīng)該放到 if else 內(nèi)部更合適。而對于 HttpURLConnection 卻沒有任何操作缩挑,為什么呢但两?
如果用Fiddler 或 Charles對數(shù)據(jù)抓包我們會發(fā)現(xiàn),我們會發(fā)現(xiàn) HttpURLConnection 默認(rèn)是有 User-Agent 的供置,類似:
Dalvik/1.6.0 (Linux; U; Android 4.1.1; Google Nexus 4 - 4.1.1 - API 16 - 768x1280_1 Build/JRO03S)
經(jīng)常用 WebView 的同學(xué)會也許會發(fā)現(xiàn)似曾相識谨湘,是的,WebView 默認(rèn)的 User-Agent 也是這個。實(shí)際在請求發(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ù)
@Override
public Map getHeaders() throws AuthFailureError {
// self-defined user agent
Map headerMap = new HashMap();
headerMap.put("User-Agent", "android-open-project-analysis/1.0");
return headerMap;
}
4.2.2 Request.java
代表一個網(wǎng)絡(luò)請求的抽象類憾儒。我們通過構(gòu)建一個Request類的非抽象子類(StringRequest、JsonRequest乃沙、ImageRequest 或自定義)對象起趾,并將其加入到·RequestQueue·中來完成一次網(wǎng)絡(luò)請求操作。
Volley 支持 8 種 Http 請求方式GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, PATCH
Request 類中包含了請求 url崔涂,請求請求方式阳掐,請求 Header,請求 Body冷蚂,請求的優(yōu)先級等信息缭保。
因?yàn)槭浅橄箢悾宇惐仨氈貙懙膬蓚€方法蝙茶。
abstract protected Response parseNetworkResponse(NetworkResponse response);
子類重寫此方法艺骂,將網(wǎng)絡(luò)返回的原生字節(jié)內(nèi)容,轉(zhuǎn)換成合適的類型隆夯。此方法會在工作線程中被調(diào)用钳恕。
abstract protected void deliverResponse(T response);
子類重寫此方法,將解析成合適類型的內(nèi)容傳遞給它們的監(jiān)聽回調(diào)蹄衷。
以下兩個方法也經(jīng)常會被重寫
public byte[] getBody()
重寫此方法忧额,可以構(gòu)建用于 POST、PUT愧口、PATCH 請求方式的 Body 內(nèi)容睦番。
protected Map getParams()
在上面getBody函數(shù)沒有被重寫情況下,此方法的返回值會被 key耍属、value 分別編碼后拼裝起來轉(zhuǎn)換為字節(jié)碼作為 Body 內(nèi)容托嚣。
4.2.3 RequestQueue.java
Volley 框架的核心類,將請求 Request 加入到一個運(yùn)行的RequestQueue中厚骗,來完成請求操作示启。
(1). 主要成員變量
RequestQueue 中維護(hù)了兩個基于優(yōu)先級的 Request 隊(duì)列,緩存請求隊(duì)列和網(wǎng)絡(luò)請求隊(duì)列领舰。
放在緩存請求隊(duì)列中的 Request夫嗓,將通過緩存獲取數(shù)據(jù)迟螺;放在網(wǎng)絡(luò)請求隊(duì)列中的 Request,將通過網(wǎng)絡(luò)獲取數(shù)據(jù)啤月。
private final PriorityBlockingQueue> mCacheQueue = new PriorityBlockingQueue>();
private final PriorityBlockingQueue> mNetworkQueue = new PriorityBlockingQueue>();
維護(hù)了一個正在進(jìn)行中煮仇,尚未完成的請求集合劳跃。
private final Set> mCurrentRequests = new HashSet>();
維護(hù)了一個等待請求的集合谎仲,如果一個請求正在被處理并且可以被緩存,后續(xù)的相同 url 的請求刨仑,將進(jìn)入此等待隊(duì)列郑诺。
private final Map>> mWaitingRequests = new HashMap>>();
(2). 啟動隊(duì)列
創(chuàng)建出 RequestQueue 以后,調(diào)用 start 方法杉武,啟動隊(duì)列辙诞。
/**
* Starts the dispatchers in this queue.
*/
public void start() {
stop();? // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
start 方法中,開啟一個緩存調(diào)度線程CacheDispatcher和 n 個網(wǎng)絡(luò)調(diào)度線程N(yùn)etworkDispatcher轻抱,這里 n 默認(rèn)為 4飞涂,存在優(yōu)化的余地,比如可以根據(jù) CPU 核數(shù)以及網(wǎng)絡(luò)類型計(jì)算更合適的并發(fā)數(shù)祈搜。
緩存調(diào)度線程不斷的從緩存請求隊(duì)列中取出 Request 去處理较店,網(wǎng)絡(luò)調(diào)度線程不斷的從網(wǎng)絡(luò)請求隊(duì)列中取出 Request 去處理。
(3). 加入請求
public Request add(Request request);
流程圖如下:
(4). 請求完成
void finish(Request request)
Request 請求結(jié)束
(1). 首先從正在進(jìn)行中請求集合mCurrentRequests中移除該請求容燕。
(2). 然后查找請求等待集合mWaitingRequests中是否存在等待的請求梁呈,如果存在,則將等待隊(duì)列移除蘸秘,并將等待隊(duì)列所有的請求添加到緩存請求隊(duì)列中官卡,讓緩存請求處理線程CacheDispatcher自動處理。
(5). 請求取消
public void cancelAll(RequestFilter filter)
public void cancelAll(final Object tag)
取消當(dāng)前請求集合中所有符合條件的請求醋虏。
filter 參數(shù)表示可以按照自定義的過濾器過濾需要取消的請求寻咒。
tag 表示按照Request.setTag設(shè)置好的 tag 取消請求,比如同屬于某個 Activity 的颈嚼。
4.2.4 CacheDispatcher.java
一個線程毛秘,用于調(diào)度處理走緩存的請求。啟動后會不斷從緩存請求隊(duì)列中取請求處理粘舟,隊(duì)列為空則等待熔脂,請求處理結(jié)束則將結(jié)果傳遞給ResponseDelivery去執(zhí)行后續(xù)處理。當(dāng)結(jié)果未緩存過柑肴、緩存失效或緩存需要刷新的情況下霞揉,該請求都需要重新進(jìn)入NetworkDispatcher去調(diào)度處理。
(1). 成員變量
BlockingQueue> mCacheQueue緩存請求隊(duì)列
BlockingQueue> mNetworkQueue網(wǎng)絡(luò)請求隊(duì)列
Cache mCache緩存類晰骑,代表了一個可以獲取請求結(jié)果适秩,存儲請求結(jié)果的緩存
ResponseDelivery mDelivery請求結(jié)果傳遞類
(2). 處理流程圖
4.2.5 NetworkDispatcher.java
一個線程绊序,用于調(diào)度處理走網(wǎng)絡(luò)的請求。啟動后會不斷從網(wǎng)絡(luò)請求隊(duì)列中取請求處理秽荞,隊(duì)列為空則等待骤公,請求處理結(jié)束則將結(jié)果傳遞給 ResponseDelivery 去執(zhí)行后續(xù)處理,并判斷結(jié)果是否要進(jìn)行緩存扬跋。
(1). 成員變量
BlockingQueue> mQueue網(wǎng)絡(luò)請求隊(duì)列
Network mNetwork網(wǎng)絡(luò)類阶捆,代表了一個可以執(zhí)行請求的網(wǎng)絡(luò)
Cache mCache緩存類,代表了一個可以獲取請求結(jié)果钦听,存儲請求結(jié)果的緩存
ResponseDelivery mDelivery請求結(jié)果傳遞類洒试,可以傳遞請求的結(jié)果或者錯誤到調(diào)用者
(2). 處理流程圖
4.2.6 Cache.java
緩存接口,代表了一個可以獲取請求結(jié)果朴上,存儲請求結(jié)果的緩存垒棋。
(1). 主要方法:
public Entry get(String key);通過 key 獲取請求的緩存實(shí)體
public void put(String key, Entry entry);存入一個請求的緩存實(shí)體
public void remove(String key);移除指定的緩存實(shí)體
public void clear();清空緩存
(2). 代表緩存實(shí)體的內(nèi)部類 Entry
成員變量和方法
byte[] data請求返回的數(shù)據(jù)(Body 實(shí)體)
String etagHttp 響應(yīng)首部中用于緩存新鮮度驗(yàn)證的 ETag
long serverDateHttp 響應(yīng)首部中的響應(yīng)產(chǎn)生時(shí)間
long ttl緩存的過期時(shí)間
long softTtl緩存的新鮮時(shí)間
Map responseHeaders響應(yīng)的 Headers
boolean isExpired()判斷緩存是否過期,過期緩存不能繼續(xù)使用
boolean refreshNeeded()判斷緩存是否新鮮痪宰,不新鮮的緩存需要發(fā)到服務(wù)端做新鮮度的檢測
4.2.7 DiskBasedCache.java
繼承 Cache 類叼架,基于 Disk 的緩存實(shí)現(xiàn)類。
(1). 主要方法:
public synchronized void initialize()初始化衣撬,掃描緩存目錄得到所有緩存數(shù)據(jù)摘要信息放入內(nèi)存乖订。
public synchronized Entry get(String key)從緩存中得到數(shù)據(jù)。先從摘要信息中得到摘要信息淮韭,然后讀取緩存數(shù)據(jù)文件得到內(nèi)容垢粮。
public synchronized void put(String key, Entry entry)將數(shù)據(jù)存入緩存內(nèi)。先檢查緩存是否會滿靠粪,會則先刪除緩存中部分?jǐn)?shù)據(jù)蜡吧,然后再新建緩存文件。
private void pruneIfNeeded(int neededSpace)檢查是否能再分配 neededSpace 字節(jié)的空間占键,如果不能則刪除緩存中部分?jǐn)?shù)據(jù)昔善。
public synchronized void clear()清空緩存。public synchronized void remove(String key)刪除緩存中某個元素畔乙。
(2). CacheHeader 類
CacheHeader 是緩存文件摘要信息君仆,存儲在緩存文件的頭部,與上面的Cache.Entry相似牲距。
4.2.8 NoCache.java
繼承 Cache 類返咱,不做任何操作的緩存實(shí)現(xiàn)類,可將它作為構(gòu)建RequestQueue的參數(shù)以實(shí)現(xiàn)一個不帶緩存的請求隊(duì)列牍鞠。
4.2.9 Network.java
代表網(wǎng)絡(luò)的接口咖摹,處理網(wǎng)絡(luò)請求。
唯一的方法难述,用于執(zhí)行特定請求萤晴。
public NetworkResponse performRequest(Request request) throws VolleyError;
4.2.10 NetworkResponse.java
Network中方法 performRequest 的返回值吐句,Request的 parseNetworkResponse(…) 方法入?yún)ⅲ?Volley 中用于內(nèi)部 Response 轉(zhuǎn)換的一級店读。
封裝了網(wǎng)絡(luò)請求響應(yīng)的 StatusCode嗦枢,Headers 和 Body 等。
(1). 成員變量
int statusCodeHttp 響應(yīng)狀態(tài)碼
byte[] dataBody 數(shù)據(jù)
Map headers響應(yīng) Headers
boolean notModified表示是否為 304 響應(yīng)
long networkTimeMs請求耗時(shí)
(2). Volley 的內(nèi)部 Response 轉(zhuǎn)換流程圖
從上到下表示從得到數(shù)據(jù)后一步步的處理屯断,箭頭旁的注釋表示該步處理后的實(shí)體類文虏。
4.2.11 BasicNetwork.java
實(shí)現(xiàn) Network,Volley 中默認(rèn)的網(wǎng)絡(luò)接口實(shí)現(xiàn)類裹纳。調(diào)用HttpStack處理請求择葡,并將結(jié)果轉(zhuǎn)換為可被ResponseDelivery處理的NetworkResponse紧武。
主要實(shí)現(xiàn)了以下功能:
(1). 利用 HttpStack 執(zhí)行網(wǎng)絡(luò)請求剃氧。
(2). 如果 Request 中帶有實(shí)體信息,如 Etag,Last-Modify 等阻星,則進(jìn)行緩存新鮮度的驗(yàn)證朋鞍,并處理 304(Not Modify)響應(yīng)。
(3). 如果發(fā)生超時(shí)妥箕,認(rèn)證失敗等錯誤滥酥,進(jìn)行重試操作,直到成功畦幢、拋出異常(不滿足重試策略等)結(jié)束坎吻。
4.2.12 HttpStack.java
用于處理 Http 請求,返回請求結(jié)果的接口宇葱。目前 Volley 中的實(shí)現(xiàn)有基于 HttpURLConnection 的 HurlStack 和 基于 Apache HttpClient 的 HttpClientStack瘦真。
唯一方法,執(zhí)行請求
public HttpResponse performRequest(Request request, Map additionalHeaders)
throws IOException, AuthFailureError;
執(zhí)行 Request 代表的請求黍瞧,第二個參數(shù)表示發(fā)起請求之前诸尽,添加額外的請求 Headers。
4.2.13 HttpClientStack.java
實(shí)現(xiàn) HttpStack 接口印颤,利用 Apache 的 HttpClient 進(jìn)行各種請求方式的請求您机。
基本就是 org.apache.http 包下面相關(guān)類的常見用法,不做詳解年局,不過與下面 HttpURLConnection 做下對比就能發(fā)現(xiàn) HttpURLConnection 的 API 相對簡單的多际看。
4.2.14 HurlStack.java
實(shí)現(xiàn) HttpStack 接口,利用 Java 的 HttpURLConnection 進(jìn)行各種請求方式的請求矢否。
4.2.15 Response.java
封裝了經(jīng)過解析后的數(shù)據(jù)仲闽,用于傳輸。并且有兩個內(nèi)部接口 Listener 和 ErrorListener 分別可表示請求失敗和成功后的回調(diào)兴喂。
Response 的構(gòu)造函數(shù)被私有化蔼囊,而通過兩個函數(shù)名更易懂的靜態(tài)方法構(gòu)建對象焚志。
4.2.16 ByteArrayPool.java
byte[] 的回收池,用于 byte[] 的回收再利用畏鼓,減少了內(nèi)存的分配和回收酱酬。 主要通過一個元素長度從小到大排序的ArrayList作為 byte[] 的緩存,另有一個按使用時(shí)間先后排序的ArrayList屬性用于緩存滿時(shí)清理元素云矫。
public synchronized void returnBuf(byte[] buf)
將用過的 byte[] 回收膳沽,根據(jù) byte[] 長度按照從小到大的排序?qū)?byte[] 插入到緩存中合適位置。
public synchronized byte[] getBuf(int len)
獲取長度不小于 len 的 byte[]让禀,遍歷緩存挑社,找出第一個長度大于傳入?yún)?shù)len的 byte[],并返回巡揍;如果最終沒有合適的 byte[]痛阻,new 一個返回。
private synchronized void trim()
當(dāng)緩存的 byte 超過預(yù)先設(shè)置的大小時(shí)腮敌,按照先進(jìn)先出的順序刪除最早的 byte[]阱当。
4.2.17 PoolingByteArrayOutputStream.java
繼承 ByteArrayOutputStream,原始 ByteArrayOutputStream 中用于接受寫入 bytes 的 buf糜工,每次空間不足時(shí)便會 new 更大容量的 byte[]弊添,而 PoolingByteArrayOutputStream 使用了 ByteArrayPool 作為 Byte[] 緩存來減少這種操作,從而提高性能捌木。
4.2.18 HttpHeaderParser.java
Http header 的解析工具類油坝,在 Volley 中主要作用是用于解析 Header 從而判斷返回結(jié)果是否需要緩存,如果需要返回 Header 中相關(guān)信息刨裆。
有三個方法
public static long parseDateAsEpoch(String dateStr)
解析時(shí)間澈圈,將 RFC1123 的時(shí)間格式,解析成 epoch 時(shí)間
public static String parseCharset(Map headers)
解析編碼集崔拥,在 Content-Type 首部中獲取編碼集极舔,如果沒有找到,默認(rèn)返回 ISO-8859-1
public static Cache.Entry parseCacheHeaders(NetworkResponse response)
比較重要的方法链瓦,通過網(wǎng)絡(luò)響應(yīng)中的緩存控制 Header 和 Body 內(nèi)容拆魏,構(gòu)建緩存實(shí)體。如果 Header 的 Cache-Control 字段含有no-cache或no-store表示不緩存慈俯,返回 null渤刃。
(1). 根據(jù) Date 首部,獲取響應(yīng)生成時(shí)間
(2). 根據(jù) ETag 首部贴膘,獲取響應(yīng)實(shí)體標(biāo)簽
(3). 根據(jù) Cache-Control 和 Expires 首部卖子,計(jì)算出緩存的過期時(shí)間,和緩存的新鮮度時(shí)間
兩點(diǎn)需要說明下:
1.沒有處理Last-Modify首部刑峡,而是處理存儲了Date首部洋闽,并在后續(xù)的新鮮度驗(yàn)證時(shí)玄柠,使用Date來構(gòu)建If-Modified-Since。 這與 Http 1.1 的語義有些違背诫舅。
2.計(jì)算過期時(shí)間羽利,Cache-Control 首部優(yōu)先于 Expires 首部。
4.2.19 RetryPolicy.java
重試策略接口
有三個方法:
public int getCurrentTimeout();
獲取當(dāng)前請求用時(shí)(用于 Log)
public int getCurrentRetryCount();
獲取已經(jīng)重試的次數(shù)(用于 Log)
public void retry(VolleyError error) throws VolleyError;
確定是否重試刊懈,參數(shù)為這次異常的具體信息这弧。在請求異常時(shí)此接口會被調(diào)用,可在此函數(shù)實(shí)現(xiàn)中拋出傳入的異常表示停止重試虚汛。
4.2.20 DefaultRetryPolicy.java
實(shí)現(xiàn) RetryPolicy匾浪,Volley 默認(rèn)的重試策略實(shí)現(xiàn)類。主要通過在 retry(…) 函數(shù)中判斷重試次數(shù)是否達(dá)到上限確定是否繼續(xù)重試卷哩。
其中mCurrentRetryCount變量表示已經(jīng)重試次數(shù)蛋辈。
mBackoffMultiplier表示每次重試之前的 timeout 該乘以的因子。
mCurrentTimeoutMs變量表示當(dāng)前重試的 timeout 時(shí)間殉疼,會以mBackoffMultiplier作為因子累計(jì)前幾次重試的 timeout梯浪。
4.2.21 ResponseDelivery.java
請求結(jié)果的傳輸接口,用于傳遞請求結(jié)果或者請求錯誤瓢娜。
有三個方法:
public void postResponse(Request request, Response response);
此方法用于傳遞請求結(jié)果,request和response參數(shù)分別表示請求信息和返回結(jié)果信息礼预。
public void postResponse(Request request, Response response, Runnable runnable);
此方法用于傳遞請求結(jié)果眠砾,并在完成傳遞后執(zhí)行 Runnable。
public void postError(Request request, VolleyError error);
此方法用于傳輸請求錯誤托酸。
4.2.22 ExecutorDelivery.java
請求結(jié)果傳輸接口具體實(shí)現(xiàn)類褒颈。
在 Handler 對應(yīng)線程中傳輸緩存調(diào)度線程或者網(wǎng)絡(luò)調(diào)度線程中產(chǎn)生的請求結(jié)果或請求錯誤,會在請求成功的情況下調(diào)用 Request.deliverResponse(…) 函數(shù)励堡,失敗時(shí)調(diào)用 Request.deliverError(…) 函數(shù)谷丸。
4.2.23 StringRequest.java
繼承 Request 類,代表了一個返回值為 String 的請求。將網(wǎng)絡(luò)返回的結(jié)果數(shù)據(jù)解析為 String 類型应结。通過構(gòu)造函數(shù)的 listener 傳參刨疼,支持請求成功后的 onResponse(…) 回調(diào)。
4.2.24 JsonRequest.java
抽象類鹅龄,繼承自 Request揩慕,代表了 body 為 JSON 的請求。提供了構(gòu)建 JSON 請求參數(shù)的方法扮休。
4.2.25 JsonObjectRequest.java
繼承自 JsonRequest迎卤,將網(wǎng)絡(luò)返回的結(jié)果數(shù)據(jù)解析為 JSONObject 類型。
4.2.26 JsonArrayRequest.java
繼承自 JsonRequest玷坠,將網(wǎng)絡(luò)返回的結(jié)果數(shù)據(jù)解析為 JSONArray 類型蜗搔。
4.2.27 ImageRequest.java
繼承 Request 類劲藐,代表了一個返回值為 Image 的請求。將網(wǎng)絡(luò)返回的結(jié)果數(shù)據(jù)解析為 Bitmap 類型樟凄。
可以設(shè)置圖片的最大寬度和最大高度瘩燥,并計(jì)算出合適尺寸返回。每次最多解析一張圖片防止 OOM不同。
4.2.28 ImageLoader.java
封裝了 ImageRequst 的方便使用的圖片加載工具類厉膀。
1.可以設(shè)置自定義的ImageCache,可以是內(nèi)存緩存二拐,也可以是 Disk 緩存服鹅,將獲取的圖片緩存起來,重復(fù)利用百新,減少請求企软。
2.可以定義圖片請求過程中顯示的圖片和請求失敗后顯示的圖片。
3.相同請求(相同地址饭望,相同大姓躺凇)只發(fā)送一個,可以避免重復(fù)請求铅辞。
// TODO
4.2.29 NetworkImageView.java
利用 ImageLoader厌漂,可以加載網(wǎng)絡(luò)圖片的 ImageView
有三個公開的方法:
public void setDefaultImageResId(int defaultImage)
設(shè)置默認(rèn)圖片,加載圖片過程中顯示斟珊。
public void setErrorImageResId(int errorImage)
設(shè)置錯誤圖片苇倡,加載圖片失敗后顯示。
public void setImageUrl(String url, ImageLoader imageLoader)
設(shè)置網(wǎng)絡(luò)圖片的 Url 和 ImageLoader囤踩,將利用這個 ImageLoader 去獲取網(wǎng)絡(luò)圖片旨椒。
如果有新的圖片加載請求,會把這個 ImageView 上舊的加載請求取消堵漱。
4.2.30 ClearCacheRequest.java
用于人為清空 Http 緩存的請求综慎。
添加到 RequestQueue 后能很快執(zhí)行,因?yàn)閮?yōu)先級很高勤庐,為Priority.IMMEDIATE示惊。并且清空緩存的方法mCache.clear()寫在了isCanceled()方法體中,能最早的得到執(zhí)行埃元。
ClearCacheRequest 的寫法不敢茍同涝涤,目前看來唯一的好處就是可以將清空緩存操作也當(dāng)做一個請求。而在isCanceled()中做清空操作本身就造成了歧義岛杀,不看源碼沒人知道在NetworkDispatcherrun 方法循環(huán)的過程中阔拳,isCanceled()這個讀操作竟然做了可能造成緩存被清空。只能跟源碼的解釋一樣當(dāng)做一個 Hack 操作。
4.2.31 Authenticator.java
身份認(rèn)證接口糊肠,用于基本認(rèn)證或者摘要認(rèn)證辨宠。這個類是 Volley 用于和身份驗(yàn)證打通的接口,比如 OAuth货裹,不過目前的使用不是特別廣泛和 Volley 的內(nèi)部結(jié)合也不是特別緊密嗤形。
4.2.32 AndroidAuthenticator.java
繼承 Authenticator,基于 Android AccountManager 的認(rèn)證交互實(shí)現(xiàn)類弧圆。
4.2.33 VolleyLog.java
Volley 的 Log 工具類赋兵。
4.2.34 VolleyError.java
Volley 中所有錯誤異常的父類,繼承自 Exception搔预,可通過此類設(shè)置和獲取 NetworkResponse 或者請求的耗時(shí)霹期。
4.2.35 AuthFailureError.java
繼承自 VolleyError,代表請求認(rèn)證失敗錯誤拯田,如 RespondeCode 的 401 和 403历造。
4.2.36 NetworkError.java
繼承自 VolleyError,代表網(wǎng)絡(luò)錯誤船庇。
4.2.37 ParseError.java
繼承自 VolleyError吭产,代表內(nèi)容解析錯誤。
4.2.38 ServerError.java
繼承自 VolleyError鸭轮,代表服務(wù)端錯誤臣淤。
4.2.39 TimeoutError.java
繼承自 VolleyError,代表請求超時(shí)錯誤张弛。
4.2.40 NoConnectionError.java
繼承自 NetworkError荒典,代表無法建立連接錯誤。
5. 雜談
5.1 關(guān)于 Http 緩存
Volley 構(gòu)建了一套相對完整的符合 Http 語義的緩存機(jī)制吞鸭。
優(yōu)點(diǎn)和特點(diǎn)
(1). 根據(jù)Cache-Control和Expires首部來計(jì)算緩存的過期時(shí)間。如果兩個首部都存在情況下覆糟,以Cache-Control為準(zhǔn)刻剥。
(2). 利用If-None-Match和If-Modified-Since對過期緩存或者不新鮮緩存,進(jìn)行請求再驗(yàn)證滩字,并處理 304 響應(yīng)造虏,更新緩存。
(3). 默認(rèn)的緩存實(shí)現(xiàn)麦箍,將緩存以文件的形式存儲在 Disk漓藕,程序退出后不會丟失。
我個人認(rèn)為的不足之處
緩存的再驗(yàn)證方面挟裂,在構(gòu)建If-Modified-Since請求首部時(shí)享钞,Volley 使用了服務(wù)端響應(yīng)的Date首部,沒有使用Last-Modified首部诀蓉。整個框架沒有使用Last-Modified首部栗竖。這與 Http 語義不符暑脆。
private void addCacheHeaders(Map headers, Cache.Entry entry) {
// If there's no cache entry, we're done.
if (entry == null) {
return;
}
if (entry.etag != null) {
headers.put("If-None-Match", entry.etag);
}
if (entry.serverDate > 0) {
Date refTime = new Date(entry.serverDate);
headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
}
}
服務(wù)端根據(jù)請求時(shí)通過If-Modified-Since首部傳過來的時(shí)間,判斷資源文件是否在If-Modified-Since時(shí)間以后有改動狐肢,如果有改動添吗,返回新的請求結(jié)果娜膘。如果沒有改動秉宿,返回 304 not modified衣陶。
Last-Modified代表了資源文件的最后修改時(shí)間樟氢。通常使用這個首部構(gòu)建If-Modified-Since的時(shí)間甘桑。
Date代表了響應(yīng)產(chǎn)生的時(shí)間茬高,正常情況下Date時(shí)間在Last-Modified時(shí)間之后晨缴。也就是Date>=Last-Modified讨越。
通過以上原理想邦,既然Date>=Last-Modified裤纹。那么我利用Date構(gòu)建,也是完全正確的丧没。
可能的問題出在服務(wù)端的 Http 實(shí)現(xiàn)上鹰椒,如果服務(wù)端完全遵守 Http 語義,采用時(shí)間比較的方式來驗(yàn)證If-Modified-Since呕童,判斷服務(wù)器資源文件修改時(shí)間是不是在If-Modified-Since之后漆际。那么使用Date完全正確。
可是有的服務(wù)端實(shí)現(xiàn)不是比較時(shí)間夺饲,而是直接的判斷服務(wù)器資源文件修改時(shí)間奸汇,是否和If-Modified-Since所傳時(shí)間相等。這樣使用Date就不能實(shí)現(xiàn)正確的再驗(yàn)證往声,因?yàn)镈ate的時(shí)間總不會和服務(wù)器資源文件修改時(shí)間相等擂找。
盡管使用Date可能出現(xiàn)的不正確情況,歸結(jié)于服務(wù)端沒有正確的實(shí)現(xiàn) Http 語義浩销。
但我還是希望 Volley 也能完全正確的實(shí)現(xiàn) Http 語義贯涎,至少同時(shí)處理Last-Modified和Date,并且優(yōu)先使用Last-Modified。
5.2 Bug
(1). BasicNetwork.performRequest(…)
如下代碼:
@Override
public NetworkResponse performRequest(Request request) throws VolleyError {
……
while (true) {
……
try {
……
} catch (IOException e) {
int statusCode = 0;
NetworkResponse networkResponse = null;
……
if (responseContents != null) {
……
} else {
throw new NetworkError(networkResponse);
}
}
}
}
BasicNetwork.performRequest(…) 最后的
throw new NetworkError(networkResponse);
應(yīng)該是
throw new NetworkError(e);
更合理慢洋。
原文地址:http://www.codekk.com/open-source-project-analysis/detail/Android/grumoon/Volley%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90