前言
從去年開(kāi)始使用Volley,到現(xiàn)在一年多了强霎。前幾天參加某互聯(lián)網(wǎng)公司校招被問(wèn)到Volley相對(duì)其他的網(wǎng)絡(luò)框架有什么優(yōu)缺點(diǎn),它分別是如何實(shí)現(xiàn)的哼转。當(dāng)時(shí)答得的并不好。所以趁十一假期讀一下Volley的源碼拓春。
寫這篇文章的目的有兩個(gè):1. 總結(jié)下 Android 網(wǎng)絡(luò)編程,學(xué)習(xí) Volley 設(shè)計(jì)思想释簿。2. 給正在使用 Volley 但仍然心存疑惑的人一些更深入的解析。
- Volley 到底是什么
- Volley 好在哪
- Volley 的使用場(chǎng)景和使用方式
- HTTP 權(quán)威指南筆記
- HttpURLConnection 與 HttpClient
- Volley 源碼解析
- 最后
Volley到底是什么
Volley Github主頁(yè)
Android 網(wǎng)絡(luò)通信框架Volley簡(jiǎn)介(Google IO 2013)
Volley簡(jiǎn)介
volley 是 Goole I/O 2013上發(fā)布的網(wǎng)絡(luò)通信庫(kù)硼莽,使網(wǎng)絡(luò)通信更快、更簡(jiǎn)單煮纵、更健壯懂鸵。
關(guān)鍵詞:數(shù)據(jù)不大但通信頻繁
Volley名稱的由來(lái): a burst or emission of many things or a large amount at once
Volley提供的功能
- Json,圖像等異步下載
- 網(wǎng)絡(luò)請(qǐng)求的排序(scheduling)
- 網(wǎng)絡(luò)請(qǐng)求的優(yōu)先級(jí)處理
- 緩存
- 多級(jí)別取消請(qǐng)求
- 和 Activity 的生命周期聯(lián)動(dòng)(Activity 結(jié)束時(shí)同時(shí)取消所有網(wǎng)絡(luò)請(qǐng)求)
Volley好在哪
HttpClient行疏、HttpURLConnection匆光、OKHttp和Volley優(yōu)缺點(diǎn)和性能對(duì)比
物理質(zhì)量
- 使用Volley 需要Volley.jar(120k),加上自己的封裝最多140k酿联。
- 使用OkHttp需要 okio.jar (80k), okhttp.jar(330k)這2個(gè)jar包终息,總大小差不多400k,加上自己的封裝,差不多得410k贞让。
Volley 的優(yōu)點(diǎn)
- 非常適合進(jìn)行數(shù)據(jù)量不大周崭,但通信頻繁的網(wǎng)絡(luò)操作
- 可直接在主線程調(diào)用服務(wù)端并處理返回結(jié)果
- 可以取消請(qǐng)求,容易擴(kuò)展喳张,面向接口編程
- 網(wǎng)絡(luò)請(qǐng)求線程N(yùn)etworkDispatcher默認(rèn)開(kāi)啟了4個(gè)续镇,可以優(yōu)化,通過(guò)手機(jī)CPU數(shù)量
- 通過(guò)使用標(biāo)準(zhǔn)的HTTP緩存機(jī)制保持磁盤和內(nèi)存響應(yīng)的一致
Volley 的缺點(diǎn)
- 使用的是httpclient销部、HttpURLConnection
- 6.0不支持httpclient了摸航,如果想支持得添加org.apache.http.legacy.jar
- 對(duì)大文件下載 Volley的表現(xiàn)非常糟糕
- 只支持http請(qǐng)求
- 圖片加載性能一般
Volley 的使用場(chǎng)景和使用方式
關(guān)于 Volley 怎么用網(wǎng)絡(luò)上的文章太多了,鏈接整理如下
Android Volley完全解析(一)舅桩,初識(shí)Volley的基本用法
Android Volley完全解析(二)酱虎,使用Volley加載網(wǎng)絡(luò)圖片
Android Volley完全解析(三),定制自己的Request
Android Volley 之自定義Request
官方教程(需要翻墻)
An Introduction to Volley
Http 權(quán)威指南筆記
不是要讀 Volley 的源碼嘛擂涛,怎么又看起了 HTTP 權(quán)威指南读串,原因很簡(jiǎn)單,Volley 源碼里面有很多處理是與 HTTP 協(xié)議息息相關(guān)的歼指,只有了解了協(xié)議才能更深入的理解Volley爹土。Volley里面涉及協(xié)議的地方都會(huì)在注釋中給出協(xié)議文檔的鏈接。
Hypertext Transfer Protocol -- HTTP/1.1
HTTP權(quán)威指南讀書(shū)筆記
HTTP協(xié)議詳解(真的很經(jīng)典)
HTTP協(xié)議詳解
這里簡(jiǎn)單介紹一幾個(gè)概念:
- HTTP 協(xié)議屬于應(yīng)用層協(xié)議踩身,他的基礎(chǔ)是 TCP (傳輸層)/ IP (網(wǎng)絡(luò)層)協(xié)議
- 一個(gè)HTTP事務(wù)由一條請(qǐng)求命令和一個(gè)響應(yīng)結(jié)果組成胀茵。這種通信通過(guò)名為 HTTP 報(bào)文(HTTP message)的格式化數(shù)據(jù)塊進(jìn)行
- 從Web客戶端發(fā)往Web服務(wù)器的HTTP報(bào)文稱為請(qǐng)求報(bào)文(request message)。從服務(wù)器發(fā)往客戶端的報(bào)文稱為響應(yīng)報(bào)文(reponse message)挟阻,請(qǐng)求報(bào)文和響應(yīng)報(bào)文格式類似琼娘。 HTTP報(bào)文包括以下三部分:
-
start line 起始行
*請(qǐng)求報(bào)文 包括 method + path + version
*響應(yīng)報(bào)文 包括 version + status line - headers 消息報(bào)頭峭弟,包含了很多鍵值對(duì),兩者之間用冒號(hào)(:)分隔脱拼。首部以一個(gè)空行結(jié)束瞒瘸,這里是我們開(kāi)發(fā)主要會(huì)用到的地方,特別是緩存熄浓。建議大家還是閱讀一下連接中給出的相關(guān)文章情臭。
- entity / body消息實(shí)體,空行之后就是可選的報(bào)文主體了赌蔑,其中包含了所有類型的數(shù)據(jù)俯在。起始行和首部都是文本形式且都是結(jié)構(gòu)化的,而主體則不同娃惯,主體可以包含任意的二進(jìn)制數(shù)據(jù)(圖片跷乐、視頻、音頻趾浅、軟件程序)愕提。當(dāng)然,主體還可以包含文本皿哨。
下面給出一組請(qǐng)求和響應(yīng)的樣例浅侨。
HttpURLConnection 與 HttpClient
這里了解一下 httpClient 和 HttpURLConnection 的區(qū)別和歷史,并主要學(xué)習(xí)一下 HttpURLConnection 的使用往史,android 社區(qū)現(xiàn)在更推薦使用 HttpURLConnection 來(lái)進(jìn)行網(wǎng)絡(luò)開(kāi)發(fā)仗颈。
需要注意的是 android 6.0 SDK,不再提供 org.apache.http 的支持椎例,所以 6.0 以后要想使用 Volley(HttpClient)需要手動(dòng)配置 gradle 了挨决。
HttpURLConnection(官方文檔,需要翻墻)
Interface HttpClient
A Comparison of java.net.URLConnection and HTTPClient
HttpClient和HttpURLConnection的區(qū)別
Volley 源碼解析
ok 做完了前面的準(zhǔn)備工作終于可以開(kāi)始最激動(dòng)人心的部分了订歪,首先來(lái)一張官方給出的流程圖脖祈。
對(duì)于這張圖我們只需要知道:
- Volley 運(yùn)行的過(guò)程中一共有三種線程,包括 UI 線程刷晋、Cache 調(diào)度線程和 NetWork 調(diào)度線程池
- 請(qǐng)求加入優(yōu)先級(jí)隊(duì)列盖高,Cache 線程進(jìn)行篩選,如果命中(hit)分發(fā)給 UI 線程
- 未命中(miss)交給 NetWork 調(diào)度線程池處理眼虱,取回后更新 Cache 并分發(fā)給 UI 線程
- 每次請(qǐng)求執(zhí)行過(guò)程始于 UI 線程喻奥, 終于 UI 線程
再來(lái)一張總體設(shè)計(jì)圖:
入口
我用的 Sublime Text 3 來(lái)閱讀 Volley ,可以看到 Volley 中大大小小一共43個(gè)類捏悬。
我們使用 Volley 的第一步是通過(guò)Volley 的 newRequestQueue 方法得到 一個(gè)RequestQueue 隊(duì)列撞蚕。那么我們就從這個(gè)方法開(kāi)始吧。不管幾個(gè)參數(shù)的 newRequestQueue 方法最終都會(huì)調(diào)用下面這個(gè)三個(gè)參數(shù)的过牙。
可以看到:
- 在磁盤上創(chuàng)建一塊文件
- 設(shè)置 UserAgent 甥厦,不知道什么是UserAgent去看前面的HTTP協(xié)議
- 根據(jù) SDK 版本的不同初始化 HTTPStack
- 用 HTTPStack 初始化 BasicNetwork
- 用第一步創(chuàng)建的文件初始化磁盤緩存
- 用磁盤緩存和 NetWork 創(chuàng)建我們的 請(qǐng)求隊(duì)列 RequestQueue
- 調(diào)用 RequestQueue 的 start 方法
這里面我們有幾個(gè)疑問(wèn)
- HttpStack纺铭、HurlStack、HttpClientStack 分別是啥
- NetWork刀疙、BasicNetwork 分別是啥
- DiskBasedCache 是啥
- RequestQueue 是啥舶赔,他的 start 方法做了什么事情
首先看第一個(gè) HttpStack
/**
* An HTTP stack abstraction.
*/
public interface HttpStack {
/**
* Performs an HTTP request with the given parameters.
*/
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError;
}
/**
* An HttpStack that performs request over an {@link HttpClient}.
*/
public class HttpClientStack implements HttpStack {
/**
* An {@link HttpStack} based on {@link HttpURLConnection}.
*/
public class HurlStack implements HttpStack {
HttpStack 類圖
很明顯 HttpStack 是一個(gè)接口并且只有一個(gè) performRequest 方法。
而 HurlStack 和 HttpClientStack 分別是基于 HttpUrlConnection 和 HttpClient 對(duì) HttpStack 的實(shí)現(xiàn)谦秧,是真正用來(lái)訪問(wèn)網(wǎng)絡(luò)的類竟纳。內(nèi)部具體實(shí)現(xiàn)后面再看。
下面再來(lái)看NetWork 和 BasicNetWork:
/**
* An interface for performing requests.
*/
public interface Network {
/**
* Performs the specified request.
* @param request Request to process
* @return A {@link NetworkResponse} with data and caching metadata; will never be null
* @throws VolleyError on errors
*/
public NetworkResponse performRequest(Request<?> request) throws VolleyError;
}
}
/**
* A network performing Volley requests over an {@link HttpStack}.
*/
public class BasicNetwork implements Network {
protected static final boolean DEBUG = VolleyLog.DEBUG;
private static int SLOW_REQUEST_THRESHOLD_MS = 3000;
private static int DEFAULT_POOL_SIZE = 4096;
protected final HttpStack mHttpStack;
protected final ByteArrayPool mPool;
/**
* @param httpStack HTTP stack to be used
*/
public BasicNetwork(HttpStack httpStack) {
// If a pool isn't passed in, then build a small default pool that will give us a lot of
// benefit and not use too much memory.
this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
}
/**
* @param httpStack HTTP stack to be used
* @param pool a buffer pool that improves GC performance in copy operations
*/
public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
mHttpStack = httpStack;
mPool = pool;
}
}
可以看到同樣是接口與實(shí)現(xiàn)類的關(guān)系油够,內(nèi)部封裝了一個(gè) HttpStack 用來(lái)是想網(wǎng)絡(luò)請(qǐng)求蚁袭。
接口的方法是一樣的,這又是為什么呢石咬,這里暫且不管,后面看實(shí)現(xiàn)再分析卖哎。
接下來(lái)是 DiskBasedCache:
/**
* Cache implementation that caches files directly onto the hard disk in the specified
* directory. The default disk usage size is 5MB, but is configurable.
*/
public class DiskBasedCache implements Cache {
可以看得到 DiskBaseCache 繼承自 Cache接口鬼悠,老規(guī)矩我們先看接口不看具體實(shí)現(xiàn),先知道他是干嘛的亏娜。
/**
* An interface for a cache keyed by a String with a byte array as data.
*/
public interface Cache {
/**
* an entry from the cache.
* @param key Cache key
* @return An {@link Entry} or null in the event of a cache miss
*/
public Entry get(String key);
public void put(String key, Entry entry)焕窝;
public void initialize();
public void invalidate(String key, boolean fullExpire);
public void remove(String key);
public void clear();
/**
* Data and metadata for an entry returned by the cache.
*/
public static class Entry {
/** The data returned from cache. */
public byte[] data;
/** ETag for cache coherency. */
public String etag;
/** Date of this response as reported by the server. */
public long serverDate;
/** The last modified date for the requested object. */
public long lastModified;
/** TTL for this record. */
public long ttl;
/** Soft TTL for this record. */
public long softTtl;
/** Immutable response headers as received from server; must be non-null. */
public Map<String, String> responseHeaders = Collections.emptyMap();
/** True if the entry is expired. */
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
/** True if a refresh is needed from the original data source. */
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
}
又是接口维贺,Volley 核心功能的實(shí)現(xiàn)都是基于接口的它掂。
我們來(lái)看,接口 Cache 里面封裝了一個(gè)靜態(tài)內(nèi)部類 Entry(登記)溯泣,這個(gè)內(nèi)部類非常重要虐秋,看了 HTTP 協(xié)議的同學(xué)們會(huì)發(fā)現(xiàn),Entry 里面定義的這些成員變量跟 headers(消息報(bào)頭)里面關(guān)于緩存的標(biāo)簽是一樣的垃沦,這也是前面強(qiáng)調(diào)要看協(xié)議的原因客给。其中還維護(hù)了一個(gè)map 用來(lái)保存消息報(bào)頭中的 key / value,data 來(lái)保存 entity 消息實(shí)體肢簿。除此之外就是一些集合操作了靶剑。
我們使用 Volley 的時(shí)候創(chuàng)建一個(gè) request 然后把它丟到 RequestQueue 中就可以了。那么來(lái)看 RequestQueue 的構(gòu)造方法池充,下面是最終會(huì)調(diào)用的構(gòu)造器桩引。
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
設(shè)置了幾個(gè)成員變量,那么 RequestQueue到底有哪些成員變量呢
/** Used for generating monotonically-increasing sequence numbers for requests. */
private AtomicInteger mSequenceGenerator = new AtomicInteger();
/**
* Staging area for requests that already have a duplicate request in flight.
*
* <ul>
* <li>containsKey(cacheKey) indicates that there is a request in flight for the given cache
* key.</li>
* <li>get(cacheKey) returns waiting requests for the given cache key. The in flight request
* is <em>not</em> contained in that list. Is null if no requests are staged.</li>
* </ul>
*/
private final Map<String, Queue<Request<?>>> mWaitingRequests =
new HashMap<String, Queue<Request<?>>>();
/**
* The set of all requests currently being processed by this RequestQueue. A Request
* will be in this set if it is waiting in any queue or currently being processed by
* any dispatcher.
*/
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
/** The cache triage queue. */
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<Request<?>>();
/** The queue of requests that are actually going out to the network. */
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<Request<?>>();
/** Number of network request dispatcher threads to start. */
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
/** Cache interface for retrieving and storing responses. */
private final Cache mCache;
/** Network interface for performing requests. */
private final Network mNetwork;
/** Response delivery mechanism. */
private final ResponseDelivery mDelivery;
/** The network dispatchers. */
private NetworkDispatcher[] mDispatchers;
/** The cache dispatcher. */
private CacheDispatcher mCacheDispatcher;
private List<RequestFinishedListener> mFinishedListeners =
new ArrayList<RequestFinishedListener>();
所有的成員變量以及核心方法類圖如下收夸,為了直觀方法沒(méi)有加參數(shù):
- mSequenceGenerator:序列號(hào)生成器
- mWaitingRequests:hashmap 通過(guò) method + url 為key坑匠,重復(fù) request 組成的 queue 為value
- mCurrentRequests:HashSet 存儲(chǔ)包括正在執(zhí)行和等待所有的 request
- mCacheQueue:PriorityBlockingQueue 緩存隊(duì)列
- mNetworkQueue:PriorityBlockingQueue 網(wǎng)絡(luò)請(qǐng)求隊(duì)列
- DEFAULT_NETWORK_THREAD_POOL_SIZE 網(wǎng)絡(luò)請(qǐng)求線程池大小
- mCache 接口 具體實(shí)現(xiàn)由構(gòu)造器傳入
- mNetwork 同上
- mDelivery 結(jié)果分發(fā)器
- mDispatchers 網(wǎng)絡(luò)調(diào)度數(shù)組
- mCacheDispatcher 緩存調(diào)度
RequestQueue 中一共有五個(gè)主要的方法,分別是 start咱圆、add笛辟、stop功氨、cancel、finish
我們先看 剛才遇到的 start 方法中都做了些什么
/**
* 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();
}
}
先調(diào)用了 stop手幢,然后分別調(diào)用了dispatcher 的 start
/**
* Stops the cache and network dispatchers.
*/
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
stop 調(diào)用了分別調(diào)用了 dispatcher 的quit
那么疑問(wèn)來(lái)了捷凄,dispatcher 的 quit 和 start 是干嘛呢
public class CacheDispatcher extends Thread {
public class NetworkDispatcher extends Thread {
CacheDispatcher 和 NetworkDispatcher 都繼承自Thread,start 方法自然是開(kāi)啟一個(gè)新的線程那quit围来,一定是關(guān)閉線程了跺涤,看一下 Volley 是怎么實(shí)現(xiàn)的
public void quit() {
mQuit = true;
interrupt();
}
@Override
public void run() {
while (true) {
if (mQuit) {
return;
}
}
}
我們忽略具體實(shí)現(xiàn)可以看到,run 方法里面是一個(gè) while true 的無(wú)限循環(huán)监透,然后用以個(gè)標(biāo)記字段桶错,來(lái)控制循環(huán)退出。
所以 start 方法做的的事情就很清楚了胀蛮,先 stop 掉跑著的線程院刁,然后開(kāi)啟一個(gè)緩存線程, 一組(默認(rèn)四個(gè))網(wǎng)絡(luò)線程粪狼,每個(gè)里面都有一個(gè)while ture 死循環(huán)退腥。等待 request add 到 Requestqueue 中,接下來(lái)我們就來(lái)看五個(gè)主要方法中的 add
手撕 Volley(二)