序
本文是承接Volley源碼解析之---一次完整的StringRequest請求(1)的第二篇。上一篇文章两疚,主要介紹了NetworkDispatcher
以及BasicNetwork
等舆驶,如果沒有讀過上篇文章的建議先讀上一篇文章再讀這個熊昌,才能更好地連貫起來婿屹。接下來我們將繼續(xù)講解CacheDispatcher
和RequestQueue.add
昂利。看看他們分別都干了些什么窝撵!
CacheDispatcher
在上一篇文章中我們提到碌奉,RequestQueue
的Start
方法中開啟了CacheDispatcher
赐劣。在我看來魁兼,它是用來協(xié)助緩存請求的咐汞。先來看看它的構(gòu)造函數(shù):
/**
* Creates a new cache triage dispatcher thread. You must call {@link #start()}
* in order to begin processing.
*
* @param cacheQueue Queue of incoming requests for triage
* @param networkQueue Queue to post requests that require network to
* @param cache Cache interface to use for resolution
* @param delivery Delivery interface to use for posting responses
*/
public CacheDispatcher(
BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
Cache cache, ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
}
可以看到他其實就是比NetworkDispatcher
多了一個mCacheQueue
的阻塞隊列化撕,其他三個參數(shù)的意義和NetworkDispatcher
的參數(shù)含義是一樣的植阴。這里我就不重復(fù)啰嗦了掠手。
接著我們看看它的run
方法喷鸽。
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
//設(shè)置線程的優(yōu)先級
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
//初始化緩存
mCache.initialize();
while (true) {
try {
// Get a request from the cache triage queue, blocking until
// at least one is available.從隊列里面取出請求报腔,如果沒有就阻塞
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
//判斷是否已經(jīng)被取消,如果被取消了那么就直接finish掉本次請求翻诉,進(jìn)行下一次請求
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache. 根據(jù)緩存的key從緩存里面獲取緩存的數(shù)據(jù)
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
//如果緩存中沒有找到key對應(yīng)的數(shù)據(jù),那么就將本次請求放入mNetworkQueue
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
//緩存數(shù)據(jù)不為空芦圾。接下來做相應(yīng)的操作
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
//判斷緩存是否過期俄认,如果過期了个少,同樣將請求添加到mNetworkQueue但是同時給請求設(shè)置CacheEntry
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
//緩存命中眯杏,將緩存數(shù)據(jù)轉(zhuǎn)換成Response對象
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
//判斷緩存是否需要刷新夜焦。如果不需要刷新岂贩,直接通過mDelivery將請求結(jié)果回調(diào)給請求調(diào)用者
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
//如果需要刷新茫经,則先直接將請求的結(jié)果回調(diào)給請求調(diào)用者萎津,但是同時將請求加入mNetworkQueue進(jìn)行網(wǎng)絡(luò)請求
// 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;
// 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) {
// Not much we can do about this.
}
}
});
}
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
}
}
}
同樣的上面我在必要的地方都添加了注釋卸伞,然后我們開始講解一個一個的重點锉屈。
- 第一個
mCache.initialize();
從方法名可以知道是用來初始化什么東西的荤傲,那么這個方法都初始化了什么呢弃酌。讓我們跳到方法里面看看,注默認(rèn)的Cache是DiskBasedCache
.
/**
* 掃描當(dāng)前所有的緩存文件乌询,初始化DiskBasedCache,如果根目錄不存在就創(chuàng)建根目錄
* Initializes the DiskBasedCache by scanning for all files currently in the
* specified root directory. Creates the root directory if necessary.
*/
@Override
public synchronized void initialize() {
if (!mRootDirectory.exists()) {
if (!mRootDirectory.mkdirs()) {
VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
}
return;
}
File[] files = mRootDirectory.listFiles();
if (files == null) {
return;
}
for (File file : files) {
BufferedInputStream fis = null;
try {
fis = new BufferedInputStream(new FileInputStream(file));
CacheHeader entry = CacheHeader.readHeader(fis);
entry.size = file.length();
putEntry(entry.key, entry);
} catch (IOException e) {
if (file != null) {
file.delete();
}
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ignored) { }
}
}
}
從上面的代碼我們可以看到,initialize
就是通過遍歷緩存目錄中的文件鬼佣,將緩存的請求頭讀取到內(nèi)存中即mEntries
變量中驶拱,以供之后查詢緩存使用。那么CacheHeader
都包括了那些字段呢晶衷,一起來看看CacheHeader.readHeader(fis)
方法蓝纲。
/**
* Reads the header off of an InputStream and returns a CacheHeader object.
* @param is The InputStream to read from.
* @throws IOException
*/
public static CacheHeader readHeader(InputStream is) throws IOException {
CacheHeader entry = new CacheHeader();
int magic = readInt(is);
if (magic != CACHE_MAGIC) {
// don't bother deleting, it'll get pruned eventually
throw new IOException();
}
//緩存標(biāo)示
entry.key = readString(is);
//資源的唯一標(biāo)示
entry.etag = readString(is);
if (entry.etag.equals("")) {
entry.etag = null;
}
//
entry.serverDate = readLong(is);
//上一次修改的時間
entry.lastModified = readLong(is);
//硬過期時間,緩存無效
entry.ttl = readLong(is);
//軟過期時間晌纫,雖然過期了税迷,但是緩存還能使用
entry.softTtl = readLong(is);
//響應(yīng)頭
entry.responseHeaders = readStringStringMap(is);
return entry;
}
可以看到CacheHeader
除了保存了響應(yīng)頭之外,還保存了ttl
時間以及softTtl
時間锹漱,這兩個時間都是和緩存過期期限有關(guān)系的箭养,等會我們會更詳細(xì)的解釋,先記住有這么一個東西哥牍,lastModified
是上一次資源的時間毕泌,我們可以利用這個來判斷服務(wù)器上的資源是否真的改變了。Etag
是資源的唯一標(biāo)示砂心,也可以用來判斷資源是否過期懈词。關(guān)于Etag
與lastModified
在什么情況下用來判斷資源是否過期以及如何判斷我們在上面一篇文章中已經(jīng)有詳細(xì)的說明,沒有看的同學(xué)找到這篇文章看一看哦辩诞,當(dāng)然如果你感興趣的話坎弯。想要知道緩存文件是如何存儲緩存數(shù)據(jù)的可以找到目錄里的緩存文件看看。
就這樣完成了初始化译暂,那么繼續(xù)抠忘,跟NetworkDispatcher
一樣首先從阻塞隊列里面中取出一個請求,不過這個是``mCacheQueue里面取出
Request外永,不是從
mNetworkQueue里面取崎脉,因為這是緩存請求。同樣的由于是個阻塞隊列伯顶,所以如果沒有請求 那么就阻塞等待囚灼。跟
NetworkDispatcher`一樣在處理請求之前骆膝,先判斷一下請求是否被取消,如果已經(jīng)被取消了灶体,那么就finish掉整個請求阅签,進(jìn)行下一次請求。代碼如下:
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
//判斷是否已經(jīng)被取消蝎抽,如果被取消了那么就直接finish掉本次請求政钟,進(jìn)行下一次請求
request.finish("cache-discard-canceled");
continue;
}
當(dāng)獲取了請求之后,根據(jù)請求的CacheKey從緩存中取數(shù)據(jù)樟结,如果緩存命中养交,則使用緩存的數(shù)據(jù),如果命中緩存失敗瓢宦,則將請求添加進(jìn)mNetworkQueue
進(jìn)行網(wǎng)絡(luò)請求碎连。
// Attempt to retrieve this item from cache. 根據(jù)緩存的key從緩存里面獲取緩存的數(shù)據(jù)
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
//如果緩存中沒有找到key對應(yīng)的數(shù)據(jù),那么就將本次請求放入mNetworkQueue
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
緩存命中(即有該請求的緩存數(shù)據(jù))驮履,則判斷緩存是否過期 entry.isExpired()
,如果過期了那么就將請求加入mNetworkQueue
進(jìn)行網(wǎng)絡(luò)請求破花。首先讓我們來看看,entry.isExpired()
這個方法疲吸。
/** True if the entry is expired. */
boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
原來這個方法就是判斷一下座每,ttl小于當(dāng)前時間,如果小于這說明緩存過期啦摘悴,應(yīng)該重新請求新的數(shù)據(jù)了峭梳。那么ttl是哪里來的,接下來我們還會看到softTtl
,那么它又是什么決定的蹂喻,接下來我們一起看一下葱椭。 還記得我們這個方法Response<JSONObject> parseNetworkResponse(NetworkResponse response)
嗎,這是我們將NetworkResponse
轉(zhuǎn)換成Response
的方法口四。因為我們是講解StringRequest
請求孵运,所以來看看在轉(zhuǎn)換的過程中都干了些什么。
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
注意啦蔓彩。在return的時候調(diào)用了HttpHeaderParser.parseCacheHeaders(response)
函數(shù)治笨。正如其名這個就是將Response
的轉(zhuǎn)成CacheHeader
。接下來一起來看看這個方法:
/**
* Extracts a {@link com.android.volley.Cache.Entry} from a {@link NetworkResponse}.
*
* @param response The network response to parse headers from
* @return a cache entry for the given response, or null if the response is not cacheable.
*/
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
long now = System.currentTimeMillis();
Map<String, String> headers = response.headers;
long serverDate = 0;
long lastModified = 0;
long serverExpires = 0;
long softExpire = 0;
long finalExpire = 0;
long maxAge = 0;
long staleWhileRevalidate = 0;
boolean hasCacheControl = false;
boolean mustRevalidate = false;
String serverEtag = null;
String headerValue;
//獲取服務(wù)器時間
headerValue = headers.get("Date");
if (headerValue != null) {
serverDate = parseDateAsEpoch(headerValue);
}
//獲取緩存控制字段
headerValue = headers.get("Cache-Control");
if (headerValue != null) {
hasCacheControl = true;
String[] tokens = headerValue.split(",");
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i].trim();
if (token.equals("no-cache") || token.equals("no-store")) {
//如果`no-cache`或者`no-store`都是控制不使用緩存直接向服務(wù)器請求赤嚼,都表示則直接return null
return null;
} else if (token.startsWith("max-age=")) {
//表示緩存在xxx秒之后過期旷赖。
try {
maxAge = Long.parseLong(token.substring(8));
} catch (Exception e) {
}
} else if (token.startsWith("stale-while-revalidate=")) {
//緩存過期之后還能使用該緩存的時間額度
try {
staleWhileRevalidate = Long.parseLong(token.substring(23));
} catch (Exception e) {
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
//必須在緩存過期之后立馬重新請求數(shù)據(jù)
mustRevalidate = true;
}
}
}
//獲取超期時間,不過這個是返回的服務(wù)器上面的時間為基準(zhǔn)的更卒,
// 所以如果客戶端和服務(wù)器端時間相差很大等孵,那么就會很不標(biāo)準(zhǔn),
// 所以后來在Cache-Control里面添加max-age來控制蹂空,max-age優(yōu)先級更高
headerValue = headers.get("Expires");
if (headerValue != null) {
serverExpires = parseDateAsEpoch(headerValue);
}
//資源上一次修改的時間
headerValue = headers.get("Last-Modified");
if (headerValue != null) {
lastModified = parseDateAsEpoch(headerValue);
}
//資源標(biāo)示
serverEtag = headers.get("ETag");
// Cache-Control takes precedence over an Expires header, even if both exist and Expires
// is more restrictive.
//Cache-Control優(yōu)先于Expires Header俯萌,所以先判斷是否存在CacheControl 并且沒有no-cache或no-store字段
if (hasCacheControl) {
//軟過期時間(即雖然緩存過期了但是仍然可以使用緩存的時間范圍)果录,軟超期時間等于現(xiàn)在的時間 + max-age * 1000
softExpire = now + maxAge * 1000;
//如果mustRevalidate存在,那么這個時候真正超期時間就等于軟過期時間
//不存在的話咐熙,真正超期時間 = 軟過期時間 + staleWhileRevalidate * 1000
finalExpire = mustRevalidate
? softExpire
: softExpire + staleWhileRevalidate * 1000;
} else if (serverDate > 0 && serverExpires >= serverDate) {
//Expire頭部在HTTP協(xié)議中就是軟超期時間雕憔,所以這個時候真正超期時間 == 軟超期時間
// Default semantic for Expire header in HTTP specification is softExpire.
softExpire = now + (serverExpires - serverDate);
finalExpire = softExpire;
}
Cache.Entry entry = new Cache.Entry();
entry.data = response.data;
entry.etag = serverEtag;
entry.softTtl = softExpire;
entry.ttl = finalExpire;
entry.serverDate = serverDate;
entry.lastModified = lastModified;
entry.responseHeaders = headers;
return entry;
}
從上面這個方法我們可以看到緩存中每個字段是如何計算得到的,尤其是軟超期時間和真正超期的時間糖声。我總結(jié)一下:
- 當(dāng)Response中有Cache-Control并且沒有no-cache以及no-store字段時,軟過期時間等于當(dāng)前時間+max-age * 1000 .如果存在must-revalidate或者proxy-revalidate時分瘦,則真正過期時間等于軟過期時間 + stale-while-revalidate
- 如果CacheControl 不存在蘸泻,則軟過期時間 == 真正過期時間 == softExpire = now + (serverExpires - serverDate)
- 不過值得注意的是,Expire的日期是根據(jù)服務(wù)器的時間來定的嘲玫,如果服務(wù)器和客戶端的時間相差很大的話那么時間就不一致了悦施。
而且我們可以知道ttl是最終過期時間,softTtl是軟過期時間去团。所以entry.isExpired()
為true
則說明緩存過期了抡诞,則需要重新請求網(wǎng)絡(luò),所以直接添加到mNetworkQueue
里面進(jìn)行網(wǎng)絡(luò)請求土陪。有些同學(xué)可能覺得奇怪既然都要重新請求了為什么還要把緩存中的entry添加到reqeust
這個對象呢昼汗,執(zhí)行request.setCacheEntry(entry);
呢,在上一篇我們提到過雖然緩存過期了鬼雀,但是并不代表服務(wù)器上的資源真的改變了顷窒,所以這個時候?qū)⑸弦淮蔚?code>LastModified以及etag
傳遞過去,可以用于服務(wù)器驗證資源是否過期的校驗源哩。接下來鞋吉,當(dāng)緩存沒有真正過期,則從緩存中拿出上一次響應(yīng)的數(shù)據(jù)励烦,然后轉(zhuǎn)換成Response
對象谓着。但是現(xiàn)在還不能吧結(jié)果返回給請求調(diào)用者,還需要判斷一下坛掠,接著看entry.refreshNeeded();
,從方法上來看赊锚,是判斷是否需要更新,具體看看是如何判斷的
/** True if a refresh is needed from the original data source. */
boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
可以看到這個方法是根據(jù)軟過期時間(softTtl)來判斷是否需要刷新屉栓,上面我們提到過改抡,如果超過了軟過期時間,雖然緩存還是可以用的系瓢,但是需要同時請求服務(wù)器獲取新的數(shù)據(jù)阿纤,所以接下來就是根據(jù)是否需要刷新做不同的處理,如果不需要刷新那么就直接將Response
回調(diào)給請求調(diào)用者夷陋,即StringRequest
的Listener
里面欠拾。同樣的 request.setCacheEntry(entry)
便于服務(wù)器驗證胰锌,減少不必要的數(shù)據(jù)請求。
至此藐窄,我們把CacheDispatcher
的run
方法給理清楚了资昧。
接下來,我們還剩下一個分析點沒有分析了荆忍。那就是RequestQueue.add
對于這個方法格带,我自己一開始也有幾個疑問:
- add之后做了什么
- request添加進(jìn)去之后是不是通過
CatchDispatcher
來處理的似踱。
直接看代碼澳泵,才能找到答案束世,所以接下來我們一起來看看RequestQueue.add
都做了什么吧炸卑。
RequestQueue
首先貼上add
方法的源碼:
/**
* Adds a Request to the dispatch queue.
* @param request The request to service
* @return The passed-in request
*/
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.
//設(shè)置這個請求屬于這個ReqeustQueue拳芙,并將這個請求添加到這個請求隊列的當(dāng)前請求隊列中
//mCurrentRequests 是存放當(dāng)前所有的請求的一個集合
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
//給request設(shè)置序號
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// If the request is uncacheable, skip the cache queue and go straight to the network.
//判斷是否可以緩存囊颅,如果不可以湾戳,就直接將請求添加進(jìn)mNetworkQueue進(jìn)行網(wǎng)絡(luò)請求弟劲,默認(rèn)都是true蟋软,當(dāng)然你可以設(shè)成false
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
//將請求插入Map镶摘,如果這里已經(jīng)相同的cachekey的請求正在請求中
synchronized (mWaitingRequests) {
//mWaitingRequest 是存放同一個cacheKey請求的多個請求。
String cacheKey = request.getCacheKey(); //獲取請求的cacheKey
if (mWaitingRequests.containsKey(cacheKey)) {
//已經(jīng)添加進(jìn)了隊列岳守,那么將request添加到 隊列中
// There is already a request in flight. Queue up.
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
// Insert 'null' queue for this cacheKey, indicating there is now a request in
// flight.
//mWaitingRequests里面還不存在凄敢,cacheKey的請求,則將cacheKey放入mWaitingRequest并且將當(dāng)前請求加入mCacheQueue中
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
上面我已經(jīng)添加了對應(yīng)的注釋湿痢,首先我們將Request與當(dāng)前RequestQueue
關(guān)聯(lián)起來贡未,然后將reqeust
加入mCurrentRequests
里面 我們可以看到因為每一個請求都會加進(jìn)去,所以這個集合就是記錄了所有的請求蒙袍。然后根據(jù)request是否可以緩存俊卤,如果不可緩存,那么直接添加到NetworkQueue進(jìn)行網(wǎng)絡(luò)請求害幅,否則繼續(xù)往下消恍。 可以緩存,那么先獲取到CacheKey
,然后判斷mWaitingRequests
中是否存在CacheKey
.等等這個mWaitingRequests
又是什么東東以现。在我看來狠怨,mWaitingRequests
就是存儲相同Cachekey
的Request,這樣可以避免同一種請求同時被添加進(jìn)mCacheQueue中邑遏,可以在第一個請求結(jié)束之后再添加進(jìn)cacheQueue隊列中佣赖,這樣之后的請求就可以直接從緩存中取,即快速又減少了網(wǎng)絡(luò)的重復(fù)訪問记盒。這樣是不是很好憎蛤。當(dāng)然如果現(xiàn)在mWaitingRequest
里面不包括當(dāng)前請求CacheKey
那么就添加進(jìn)mWaitingRequest
,并且將請求添加到mCacheQueue
進(jìn)行緩存請求。 其實Add方法很簡單俩檬,就是根據(jù) Request.shouldCache
判斷應(yīng)該進(jìn)行網(wǎng)絡(luò)請求還是緩存請求萎胰。那么請求添加進(jìn)去了。你好不好奇棚辽,那些重復(fù)的Request
什么時候加入CacheQueue
或者怎么處理技竟,那么就應(yīng)該一起來看看RequestQueue.finish
方法了。
/**
* Called from {@link Request#finish(String)}, indicating that processing of the given request
* has finished.
*
* <p>Releases waiting requests for <code>request.getCacheKey()</code> if
* <code>request.shouldCache()</code>.</p>
*/
<T> void finish(Request<T> request) {
// Remove from the set of requests currently being processed.
//從當(dāng)前請求集合里面移除要finish掉的請求
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
//回調(diào)finish給Listener
synchronized (mFinishedListeners) {
for (RequestFinishedListener<T> listener : mFinishedListeners) {
listener.onRequestFinished(request);
}
}
//判斷request是否shouldCache
if (request.shouldCache()) {
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
//根據(jù)cacheKey 從mWaitingRequests中移除屈藐,同時將返回的相同cachekey的請求放入mCacheQueue中榔组,
// 這樣只要緩存沒過期就可以從緩存中取數(shù)據(jù)
Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
if (waitingRequests != null) {
if (VolleyLog.DEBUG) {
VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
waitingRequests.size(), cacheKey);
}
// Process all queued up requests. They won't be considered as in flight, but
// that's not a problem as the cache has been primed by 'request'.
//從英文就可以知道,因為當(dāng)前request已經(jīng)請求過了 所以接下來的請求可以從緩存中拿響應(yīng)
mCacheQueue.addAll(waitingRequests);
}
}
}
}
看到了吧联逻,相同的CacheKey的請求就是在這里處理的哦搓扯。 終于終于把一次完整的StringRequest請求給講清楚了。在寫的過程中遣妥,我自己也對這個每一個點越來越理解,所以有時候如果你學(xué)習(xí)了一個新東西攀细,盡管網(wǎng)上有很多的資料了箫踩,但是你自己寫一下總結(jié)的文章,在寫的過程中谭贪,潛移默化中你掌握的更深本來以前不理解的地方也更加清晰境钟。所以鼓勵大家都寫起來,不為其他俭识,為了讓自己真正掌握慨削,俗話說好記性不如爛筆頭。
哈哈套媚,扯得多了點缚态,但是還沒完呢,最后一個流程圖堤瘤,給我和你們一起縷縷整個過程玫芦。見流程圖。
整體流程圖
CacheDispatcher
NetworkDispatcher
好了本辐,各位Volley源碼的分析就到此結(jié)束了桥帆。