volley源碼學(xué)習(xí)
之前一直對(duì)于源碼學(xué)習(xí)抱著一種又愛又恨的心情。愛的是因?yàn)橹涝创a有一些特別好的設(shè)計(jì)思路赏半,可以讓自己借鑒未辆,而且對(duì)于設(shè)計(jì)模式來說是最好的實(shí)戰(zhàn)場燃辖。那為啥還會(huì)恨呢,曾經(jīng)很多次下載了很多開源庫的源碼,可是看的看的就感覺云里霧里师骗,不知所蹤历等。心中沒有一個(gè)總體的框架,總感覺看的細(xì)如牛毛辟癌,一葉障目寒屯。今天又找時(shí)間翻出最簡單的volley,準(zhǔn)備從頭再看一遍愿待。沒想到收獲很多浩螺,寫下這篇文章,用來記錄仍侥。
volley是什么
volley是一個(gè)封裝好的網(wǎng)絡(luò)庫要出,是把httpclient或HttpURLConnection又封裝了一層,加上了線程农渊,隊(duì)列患蹂,緩存等機(jī)制,讓網(wǎng)絡(luò)請(qǐng)求更加容易砸紊。
volley是google官方推出的一個(gè)開源項(xiàng)目传于,專門適用于android輕量的請(qǐng)求。但是對(duì)于大文件等支持不好醉顽。
volley整個(gè)源碼都是用了接口編程的思想沼溜,這也比較符合設(shè)計(jì)模式的優(yōu)秀實(shí)踐。
所以看volley源碼會(huì)對(duì)于接口編程有較深入的理解游添。
除了volley之外系草,還有一個(gè)retrofit網(wǎng)絡(luò)框架,是對(duì)okhttp的封裝,這個(gè)框架現(xiàn)在特別火唆涝,而且如果你想領(lǐng)略設(shè)計(jì)模式之美的話找都,這個(gè)retrofit源碼必須看,等之后再寫一篇分析retrofit的文章
volley工作流程
首先先放一張google官方給的流程圖廊酣,雖然比較簡單能耻,但是可以在心中有一個(gè)大致的概念
首先我們從volley調(diào)用入手,找到調(diào)用入口亡驰,這樣就可以按圖索驥晓猛,一點(diǎn)點(diǎn)摸索出整個(gè)的流程。
RequestQueue queue = Volley.newRequestQueue(this);
String url ="https://www.gaotenglife.com";
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Display the first 500 characters of the response string.
mTextView.setText("Response is: "+ response.substring(0,500));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mTextView.setText("That didn't work!");
}
});
// Add the request to the RequestQueue.
queue.add(stringRequest);
從上面的代碼可以看出凡辱,我們首先通過newRequestQueue創(chuàng)建出一個(gè)requestqueue鞍帝,也就是請(qǐng)求隊(duì)列。然后把一個(gè)請(qǐng)求加入到請(qǐng)求隊(duì)列里面煞茫。這里可以看出帕涌,當(dāng)我們把stringrequest加入請(qǐng)求隊(duì)列后摄凡,請(qǐng)求便開始了,所以我們便從add看起蚓曼。
public <T> Request<T> add(Request<T> request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// If the request is uncacheable, skip the cache queue and go straight to the network.
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
mCacheQueue.add(request);
return request;
}
這里面主要的功能就是把網(wǎng)絡(luò)請(qǐng)求加入到對(duì)應(yīng)的網(wǎng)絡(luò)請(qǐng)求隊(duì)列(mNetworkQueue),如果設(shè)置了請(qǐng)求需要緩存的話亲澡,同時(shí)也加入到緩存隊(duì)列中。這里比較奇怪纫版,為啥只是單單加入隊(duì)列網(wǎng)絡(luò)請(qǐng)求就能發(fā)出去呢床绪。于是我們接著向下看,最有可能是在mNetworkQueue.add的時(shí)候其弊,發(fā)出請(qǐng)求癞己。于是我們繼續(xù)深入,發(fā)現(xiàn)mNetworkQueue也只是一個(gè)簡單的線程安全的隊(duì)列,也沒有做過多的操作梭伐。其實(shí)這樣也是合理的痹雅,隊(duì)列不應(yīng)該包含更多的業(yè)務(wù)邏輯在里面。于是我繼續(xù)查找糊识,是哪里持有了這個(gè)隊(duì)列绩社。
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();
}
}
于是發(fā)現(xiàn)兩個(gè)地方使用了這個(gè)mNetworkQueue隊(duì)列,CacheDispatcher赂苗,NetworkDispatcher從名字是就能看出愉耙,最有可能就是NetworkDispatcher里面進(jìn)行網(wǎng)絡(luò)隊(duì)列的處理了。于是我們看NetworkDispatcher拌滋,發(fā)現(xiàn)這個(gè)NetworkDispatcher原來就是一個(gè)線程朴沿。
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
try {
processRequest();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
}
}
}
在它的run方法里面,不斷循環(huán)調(diào)用processRequest败砂,在processRequest里面從請(qǐng)求隊(duì)列里取出request
Request<?> request = mQueue.take();//取出請(qǐng)求
......
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);//通過networt真正去請(qǐng)求網(wǎng)絡(luò)
從這里看出赌渣,單一職責(zé)設(shè)計(jì)思想。隊(duì)列只處理隊(duì)列的邏輯吠卷,網(wǎng)絡(luò)只處理網(wǎng)絡(luò)的邏輯锡垄。不交叉寫到一起沦零。
那么network里面是如何真正請(qǐng)求網(wǎng)絡(luò)的呢祭隔,因?yàn)楸容^volley是封裝了具體網(wǎng)絡(luò)請(qǐng)求庫。
于是我們看到BasicNetwork里面路操,它里面有BaseHttpStack疾渴,而BaseHttpStack是個(gè)抽象類,它的子類屯仗,比如HurlStack是封裝了
HttpURLConnection搞坝,而HttpClientStack是封裝了httpcliet底層庫。這樣network就可以根據(jù)配置魁袜,選取我們需要的底層網(wǎng)絡(luò)庫桩撮。
那么請(qǐng)求完之后敦第,返回的結(jié)果如何回調(diào)回去呢。這里又借助ResponseDelivery店量,將請(qǐng)求的結(jié)果發(fā)送到ui線程
mDelivery.postResponse(request, response);
我們再進(jìn)入ExecutorDelivery這里面
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
然后通過mResponsePoster這里面調(diào)用handler芜果,最后將結(jié)果發(fā)送給ui線程
// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
這里最后調(diào)用了request里面設(shè)置的監(jiān)聽函數(shù),最后把數(shù)據(jù)給了調(diào)用方融师。
到此我們的一個(gè)調(diào)用過程就分析完畢了右钾。是不是挺簡單的。
緩存的實(shí)現(xiàn)
在剛剛上面分析代碼的時(shí)候說到旱爆,mNetworkQueue被兩個(gè)類持有舀射,一個(gè)就是咱們已經(jīng)分析過的NetworkDispatcher,還有一個(gè)就是咱們這一節(jié)要講的CacheDispatcher。
public <T> Request<T> add(Request<T> request)
...
...
mCacheQueue.add(request);
在上面隊(duì)列add的時(shí)候怀伦,如果請(qǐng)求設(shè)置了需要緩存脆烟,request.shouldCache(),也就是這個(gè)為true空镜,那么會(huì)先將這個(gè)request加入到緩存的隊(duì)列里面浩淘。
而處理緩存隊(duì)列的,就是CacheDispatcher吴攒。和NetworkDispatcher類似张抄,這個(gè)CacheDispatcher也是一個(gè)線程,在線程的run方法里面洼怔,執(zhí)行了processRequest這個(gè)方法署惯。這里面主要處理了緩存相關(guān)邏輯
首先先從緩存隊(duì)列里面將緩存的request取出來。
final Request<?> request = mCacheQueue.take();
....省略非主要代碼
// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
然后進(jìn)行了兩個(gè)判斷镣隶,第一個(gè)就是先從緩存中獲取這個(gè)request极谊,如果沒有緩存,則將請(qǐng)求直接加入到之前的網(wǎng)絡(luò)隊(duì)列安岂,進(jìn)行網(wǎng)絡(luò)請(qǐng)求轻猖。如果有緩存,則再通過判斷entry.isExpired是否過期域那,如果緩存的request已經(jīng)過期咙边,則也加入到網(wǎng)絡(luò)請(qǐng)求的隊(duì)列中。
接下來次员,如果有緩存并且緩存沒有過期败许,則從緩存中取到之前請(qǐng)求過的數(shù)據(jù),并進(jìn)行解析淑蔚。如下
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
之后又通過了一層判斷
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Restore the interrupted status
Thread.currentThread().interrupt();
}
}
});
} else {
// request has been added to list of waiting requests
// to receive the network response from the first request once it returns.
mDelivery.postResponse(request, response);
}
}
也就是說市殷,如果緩存的數(shù)據(jù)需要刷新,那么還是需要將request發(fā)送給網(wǎng)絡(luò)隊(duì)列進(jìn)行請(qǐng)求刹衫。如果數(shù)據(jù)不需要刷新醋寝,則直接通過mDelivery將緩存的數(shù)據(jù)發(fā)送給ui線程搞挣。
這里面有兩個(gè)概念,如下
/** 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();
}
ttl和softttl這兩個(gè)是http協(xié)議里面通過header計(jì)算出來的兩個(gè)值音羞。詳細(xì)可以查看HttpHeaderParser這個(gè)類柿究。
重試策略
看到重試策略的時(shí)候,我首先自己想了下黄选,如果要我自己實(shí)現(xiàn)重試策略蝇摸,我會(huì)如何做呢,很直白的的思維就是办陷,在網(wǎng)絡(luò)請(qǐng)求失敗的時(shí)候貌夕,判斷是否有重試策略,然后在網(wǎng)絡(luò)請(qǐng)求失敗的地方民镜,重新發(fā)起網(wǎng)絡(luò)請(qǐng)求啡专。
所以我就一直按照這個(gè)思路去尋找,可以在volley網(wǎng)路失敗的地方制圈,我只找到了如下的代碼
private static void attemptRetryOnException(String logPrefix, Request<?> request,
VolleyError exception) throws VolleyError {
RetryPolicy retryPolicy = request.getRetryPolicy();
int oldTimeout = request.getTimeoutMs();
try {
retryPolicy.retry(exception);
} catch (VolleyError e) {
request.addMarker(
String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
throw e;
}
request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
}
這個(gè)函數(shù)是所有網(wǎng)絡(luò)有有異常的時(shí)候们童,會(huì)調(diào)用的。但是我發(fā)現(xiàn)這里面除了設(shè)置了重試策略的一些屬性鲸鹦,其他沒有做網(wǎng)絡(luò)請(qǐng)求操作慧库。這就奇怪了。難道網(wǎng)絡(luò)請(qǐng)求會(huì)自己發(fā)起馋嗜。這讓我百思不得其解齐板。
于是我又一遍一遍看了網(wǎng)絡(luò)請(qǐng)求的代碼,終于發(fā)現(xiàn)了端倪
原來在networkdispater調(diào)用Network進(jìn)行網(wǎng)絡(luò)請(qǐng)求的時(shí)候葛菇,network里面竟然寫了一個(gè)while循環(huán)
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
.....
.....
}
}
這樣也就能解釋了甘磨,這個(gè)循環(huán)會(huì)一直嘗試去請(qǐng)求網(wǎng)絡(luò),直到不滿足重試策略之后眯停,退出循環(huán)济舆。也就是說,這個(gè)重試策略沒有正真參與具體了重試邏輯莺债。而只是保存了自己的重試狀態(tài)滋觉,真正的重試邏輯還是網(wǎng)絡(luò)請(qǐng)求去保證。
看到這里九府,我突然感覺到寫這樣代碼的人椎瘟,真是思路別具一格覆致。而且這樣的好處顯而易見侄旬,你可以重寫重試策略,而不需要重新修改網(wǎng)絡(luò)重試的邏輯煌妈。這也就是設(shè)計(jì)模式里面策略模式比較好的運(yùn)用吧儡羔。
結(jié)論體會(huì)
通過完整的看了一遍volley源碼宣羊,體會(huì)到了幾個(gè)比較重要的思想
一個(gè)就是單一職責(zé),每一層都分開汰蜘,負(fù)責(zé)每一層應(yīng)該有的功能仇冯。互相解耦族操,底層不依賴上層苛坚。比如網(wǎng)絡(luò)請(qǐng)求Network這個(gè)類,不依賴與上層的隊(duì)列類,而具體封裝底層網(wǎng)絡(luò)請(qǐng)求的BaseHttpStack也不依賴上層Network色难∑貌眨可以讓network層很輕松的更換底層網(wǎng)絡(luò)請(qǐng)求庫。
策略模式的運(yùn)用枷莉,也讓代碼邏輯與策略分離娇昙,策略里面不依賴具體邏輯。邏輯代碼里面通過改變不同策略對(duì)象笤妙,來達(dá)到控制不同策略的目的冒掌。
面向接口的編程,整個(gè)volley各個(gè)模塊都是通過接口互相之間調(diào)用蹲盘,這樣不依賴與具體實(shí)現(xiàn)股毫,就將整個(gè)框架都搭建好了,感覺很受啟發(fā)召衔。