3.6 CacheDispatcher & Cache
從volley的工作流程圖中耍铜,我們已經(jīng)知道volley的請(qǐng)求工作流程可以分為兩個(gè)部分弟疆,一個(gè)是先通過(guò)緩存查找請(qǐng)求的數(shù)據(jù)钧排,如果沒(méi)有查找成功再通過(guò)網(wǎng)絡(luò)請(qǐng)求括勺。而上面一節(jié)已經(jīng)介紹NetworkDispatcher灯谣,已經(jīng)知道NetworkDispatcher就是負(fù)責(zé)volley中網(wǎng)絡(luò)請(qǐng)求的工作潜秋,而這節(jié)介紹的CacheDispatcher從名字就可以想象得到工作和NetworkDispatcher是類(lèi)似的,而它正是負(fù)責(zé)volley的緩存工作胎许,下面就來(lái)介紹CacheDispatcher的工作流程峻呛。
3.6.1 CacheDispatcher
CacheDispatcher的工作流程跟NetworkDispatcher是類(lèi)似的,因此參考NetworkDispatcher的工作流程可以將CacheDispatcher的工作流程分為以下幾步:
- 通過(guò)存儲(chǔ)Request的CacheQueue(繼承BlockingQueue)中取出Request
- 通過(guò)Cacha對(duì)象獲取Request相應(yīng)的緩存數(shù)據(jù)Cache.Entry
- 如果Entry為空辜窑,則說(shuō)明該Request的響應(yīng)數(shù)據(jù)沒(méi)有緩存在Cache當(dāng)中杀饵,則將Request添加到NetworkQueue當(dāng)中被NetworkDispatcher處理
- 如果Entry過(guò)期了,同樣將Request添加到NetworkQueue當(dāng)中
- 如果Entry可使用谬擦,則通過(guò)Entry構(gòu)建NetworkResponse,然后通過(guò)Request.parseNetworkResponse將NetworkResponse轉(zhuǎn)換成Response對(duì)象(這里跟NetworkDispatcher一樣)
- 通過(guò)ResponseDelivery將Response送回到主線(xiàn)程處理(還是跟NetworkDispatcher一樣)
可以看到朽缎,CacheDispatcher最后兩步和NetworkDispatcher是一樣的惨远,只不過(guò)前面獲取到NetworkResponse的方式不一樣,NetworkDispatcher是通過(guò)Network執(zhí)行Request請(qǐng)求從網(wǎng)絡(luò)上獲取到NetworkResponse话肖,而CacheDispatcher是通過(guò)Cache從緩存中提取Request對(duì)應(yīng)的NetworkResponse北秽,下面通過(guò)流程圖來(lái)看一下。
從流程圖中可以看到形式和NetworkDispatcher幾乎是一樣的最筒,因此接下來(lái)看源碼也是十分簡(jiǎn)單的贺氓。
public class CacheDispatcher extends Thread {
private static final boolean DEBUG = VolleyLog.DEBUG;
/** The queue of requests coming in for triage. */
private final BlockingQueue<Request> mCacheQueue;
/** The queue of requests going out to the network. */
private final BlockingQueue<Request> mNetworkQueue;
/** The cache to read from. */
private final Cache mCache;
/** For posting responses. */
private final ResponseDelivery mDelivery;
/** Used for telling us to die. */
private volatile boolean mQuit = false;
/**
* 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;
}
/**
* Forces this dispatcher to quit immediately. If any requests are still in
* the queue, they are not guaranteed to be processed.
*/
public void quit() {
mQuit = true;
interrupt();
}
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
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()) {
request.finish("cache-discard-canceled");
continue;
}
// 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.
mNetworkQueue.put(request);
continue;
}
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
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");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
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;
// 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;
}
continue;
}
}
}
}
跟NetworkDispatcher一樣,CacheDispatcher源碼中所需要的重點(diǎn)主要有2點(diǎn):
- 類(lèi)成員及構(gòu)造方法床蜘,這些成員是CacheDispatcher工作不可缺少的辙培,也不難,只要了解工作流程便可記住了邢锯,首先要從隊(duì)列CacheQueue中取出Request對(duì)象扬蕊,然后通過(guò)Cache獲取相應(yīng)的數(shù)據(jù),若為空或過(guò)期則將Request添加到NetworkQueue中通過(guò)網(wǎng)絡(luò)獲取丹擎,若不為空則通過(guò)ResponseDelivery將數(shù)據(jù)傳送到主線(xiàn)程
- CacheDispatcher也是一個(gè)Thread尾抑,工作都是在run中進(jìn)行的歇父,結(jié)合上面的流程,可以提取出關(guān)鍵的代碼再愈,如下所示
//1.先取出Request對(duì)象
final Request request = mCacheQueue.take();
//2.Cache獲取request相應(yīng)的緩存數(shù)據(jù)
Cache.Entry entry = mCache.get(request.getCacheKey());
//3.如果數(shù)據(jù)為空或者過(guò)期則將request添加到NetworkQueue中
if (entry == null) {
mNetworkQueue.put(request);
continue;
}
if (entry.isExpired()) {
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
//4.Request調(diào)用接口方法將NetworkResponse解析成Response對(duì)象
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
//5.ResponseDelivery將response回傳到主線(xiàn)程處理
mDelivery.postResponse(request, response);
3.6.2 Cache
我們從上面的分析中可以得知榜苫,在volley中的請(qǐng)求流程主要是分為兩個(gè)流程,一個(gè)是從緩存里提取數(shù)據(jù)的緩存流程翎冲,由CacheDispatcher實(shí)現(xiàn)垂睬,另一個(gè)是從網(wǎng)絡(luò)中獲取數(shù)據(jù),由NetworkDispatcher實(shí)現(xiàn)府适。在CacheDispatcher的工作流程中羔飞,提供緩存功能的便是Cache對(duì)象,因此我們這節(jié)來(lái)學(xué)習(xí)volley中的Cache接口以及它的實(shí)現(xiàn)類(lèi)檐春,學(xué)習(xí)流程可以對(duì)照著NetworkDispatcher中的Network類(lèi)的學(xué)習(xí)逻淌。
Cache只是一個(gè)接口,源碼很簡(jiǎn)單疟暖,主要是注意Cache里面的Entry類(lèi)卡儒,該類(lèi)是主要的緩存信息存儲(chǔ)類(lèi)。
/**
* An interface for a cache keyed by a String with a byte array as data.
*/
public interface Cache {
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;
/** 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();
}
}
}
從代碼中可以發(fā)現(xiàn)Cache就是提供了簡(jiǎn)單的增刪查基本的數(shù)據(jù)操作方法俐巴,沒(méi)什么難理解的骨望,接下來(lái)看看volley中Cache的實(shí)現(xiàn)類(lèi)DiskBasedCache。
在看DiskBasedCache源碼之前欣舵,先來(lái)回憶一下緩存一般都怎么實(shí)現(xiàn)的擎鸠。我們知道緩存一般分為兩種,一種是內(nèi)存緩存缘圈,一種是磁盤(pán)緩存劣光,而內(nèi)存緩存一般使用LruCache實(shí)現(xiàn),磁盤(pán)緩存使用DiskLruCache實(shí)現(xiàn)糟把。在LruCache中底層則是用accessOrder為true的LinkedHashMap實(shí)現(xiàn)的绢涡,DiskLruCache是利用文件的讀寫(xiě)來(lái)實(shí)現(xiàn)緩存的實(shí)現(xiàn)。
因此可以猜想遣疯,DiskBasedCache無(wú)非也是用這兩種方法進(jìn)行緩存的雄可,下面具體看一下關(guān)鍵的代碼。
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 {
/** Map of the Key, CacheHeader pairs */
private final Map<String, CacheHeader> mEntries =
new LinkedHashMap<String, CacheHeader>(16, .75f, true);
/** Total amount of space currently used by the cache in bytes. */
private long mTotalSize = 0;
/** The root directory to use for the cache. */
private final File mRootDirectory;
/** The maximum size of the cache in bytes. */
private final int mMaxCacheSizeInBytes;
/** Default maximum disk usage in bytes. */
private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
/** High water mark percentage for the cache */
private static final float HYSTERESIS_FACTOR = 0.9f;
/** Magic number for current version of cache file format. */
private static final int CACHE_MAGIC = 0x20120504;
/**
* Constructs an instance of the DiskBasedCache at the specified directory.
* @param rootDirectory The root directory of the cache.
* @param maxCacheSizeInBytes The maximum size of the cache in bytes.
*/
public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
mRootDirectory = rootDirectory;
mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}
/**
* Constructs an instance of the DiskBasedCache at the specified directory using
* the default maximum cache size of 5MB.
* @param rootDirectory The root directory of the cache.
*/
public DiskBasedCache(File rootDirectory) {
this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
}
@Override
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
// if the entry does not exist, return.
if (entry == null) {
return null;
}
File file = getFileForKey(key);
CountingInputStream cis = null;
try {
cis = new CountingInputStream(new FileInputStream(file));
CacheHeader.readHeader(cis); // eat header
byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
return entry.toCacheEntry(data);
} catch (IOException e) {
VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
remove(key);
return null;
} finally {
if (cis != null) {
try {
cis.close();
} catch (IOException ioe) {
return null;
}
}
}
}
@Override
public synchronized void put(String key, Entry entry) {
pruneIfNeeded(entry.data.length);
File file = getFileForKey(key);
try {
FileOutputStream fos = new FileOutputStream(file);
CacheHeader e = new CacheHeader(key, entry);
e.writeHeader(fos);
fos.write(entry.data);
fos.close();
putEntry(key, e);
return;
} catch (IOException e) {
}
boolean deleted = file.delete();
if (!deleted) {
VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
}
}
@Override
public synchronized void remove(String key) {
boolean deleted = getFileForKey(key).delete();
removeEntry(key);
if (!deleted) {
VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
key, getFilenameForKey(key));
}
}
public File getFileForKey(String key) {
return new File(mRootDirectory, getFilenameForKey(key));
}
/**
* 判斷緩存是否超過(guò)閾值缠犀,如果超過(guò)則刪除緩存中的數(shù)據(jù)
*/
private void pruneIfNeeded(int neededSpace) {
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
return;
}
if (VolleyLog.DEBUG) {
VolleyLog.v("Pruning old cache entries.");
}
long before = mTotalSize;
int prunedFiles = 0;
long startTime = SystemClock.elapsedRealtime();
Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, CacheHeader> entry = iterator.next();
CacheHeader e = entry.getValue();
boolean deleted = getFileForKey(e.key).delete();
if (deleted) {
mTotalSize -= e.size;
} else {
VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
e.key, getFilenameForKey(e.key));
}
iterator.remove();
prunedFiles++;
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
break;
}
}
if (VolleyLog.DEBUG) {
VolleyLog.v("pruned %d files, %d bytes, %d ms",
prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);
}
}
private void putEntry(String key, CacheHeader entry) {
if (!mEntries.containsKey(key)) {
mTotalSize += entry.size;
} else {
CacheHeader oldEntry = mEntries.get(key);
mTotalSize += (entry.size - oldEntry.size);
}
mEntries.put(key, entry);
}
/**
* Removes the entry identified by 'key' from the cache.
*/
private void removeEntry(String key) {
CacheHeader entry = mEntries.get(key);
if (entry != null) {
mTotalSize -= entry.size;
mEntries.remove(key);
}
}
……
}
從源碼中可以看出数苫,DiskBasedCache并不是單純的使用LruCache和DiskLruCache進(jìn)行緩存,而是使用了兩者的結(jié)合辨液,利用LinkedHashMap來(lái)存儲(chǔ)頭部關(guān)鍵信息文判,相當(dāng)于索引,實(shí)際存儲(chǔ)數(shù)據(jù)是利用的文件進(jìn)行操作的室梅。下面通過(guò)其中的關(guān)鍵方法來(lái)進(jìn)行解釋戏仓。
- put:拿put方法來(lái)說(shuō)明疚宇,首先調(diào)用pruneIfNeeded來(lái)保證磁盤(pán)中的緩存沒(méi)有超出閾值,然后根據(jù)key獲取到相應(yīng)的文件赏殃,將entry.data輸出到文件當(dāng)中敷待,并且通過(guò)putEntry(key, e)方法將該key對(duì)應(yīng)的關(guān)鍵信息存到LinkedHashMap中即內(nèi)存緩存中。這里的LinkedHashMap雖然沒(méi)有存儲(chǔ)實(shí)際數(shù)據(jù)仁热,但它相當(dāng)于建了一個(gè)索引榜揖,在通過(guò)get獲取某緩存數(shù)據(jù)的時(shí)候,判斷LinkedHashMap有沒(méi)有該key的信息抗蠢,如果有則說(shuō)明磁盤(pán)中有該key對(duì)應(yīng)的數(shù)據(jù)举哟,此時(shí)再去磁盤(pán)中獲取實(shí)際數(shù)據(jù)。因?yàn)長(zhǎng)inkedHashMap是存在內(nèi)存中的迅矛,因此先查找LinkedHashMap比在磁盤(pán)中查找效率高很多妨猩。
- get:上面put方法已經(jīng)介紹了LinkedHashMap的作用了,就是先通過(guò)mEntries.get(key)判斷內(nèi)存中有沒(méi)有該key對(duì)應(yīng)的數(shù)據(jù)的關(guān)鍵信息秽褒,如果有則說(shuō)明磁盤(pán)中存在該數(shù)據(jù)壶硅,此時(shí)再去磁盤(pán)中查找,提高了效率销斟。
- pruneIfNeeded:這個(gè)方法是在put方法中調(diào)用的庐椒,目的是保證磁盤(pán)緩存不超過(guò)閾值,從該方法的源碼中可以得知蚂踊,刪除緩存的依據(jù)是根據(jù)LinkedHashMap的iterator來(lái)刪除對(duì)應(yīng)數(shù)據(jù)的约谈,這里充分利用到了LinkedHashMap的LRU算法,因?yàn)閍ccessOrder為true的LinkedHashMap會(huì)將使用頻率少的鍵值對(duì)留在隊(duì)頭犁钟,因此最先刪除的就是最近最少使用的棱诱。
因此,DiskBasedCache的實(shí)現(xiàn)原理關(guān)鍵就在于利用了一個(gè)LinkedHashMap在內(nèi)存當(dāng)中當(dāng)磁盤(pán)緩存文件的索引特纤,實(shí)際緩存是通過(guò)磁盤(pán)來(lái)緩存的,這樣避免了每次查找都去進(jìn)行磁盤(pán)操作大大提高了效率侥加。
3.7 RequestQueue
從volley的工作流程中可以看出捧存,NetworkDispatcher和CacheDispatcher的分析其實(shí)就是volley的全部工作了,但是前面一直沒(méi)有介紹它們兩個(gè)是什么時(shí)候被調(diào)用工作的担败,這就是本節(jié)的內(nèi)容RequestQueue昔穴,回顧一下volley的使用方法,就會(huì)發(fā)現(xiàn)Request同時(shí)也是volley框架工作的整個(gè)入口提前,先看一下volley的使用方法吗货。
volley的使用方法
//1. 創(chuàng)建RequestQueue對(duì)象
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
//2. 創(chuàng)建Request對(duì)象并在Listener中處理回調(diào)對(duì)象,在回調(diào)方法中更新UI
StringRequest stringRequest = new StringRequest(
"http://10.132.25.104/JsonData/data1.php",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// TODO Auto-generated method stub
textView.setText(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG,error.getMessage());
}
});
//3. 將Request添加至RequestQueue
queue.add(stringRequest);
可以發(fā)現(xiàn)狈网,RequestQueue中最主要就是構(gòu)造方法和add方法宙搬,現(xiàn)在就來(lái)看一下RequestQueue的源碼是怎么實(shí)現(xiàn)的笨腥,還是只挑選重要的部分。
public class RequestQueue {
private AtomicInteger mSequenceGenerator = new AtomicInteger();
private final Map<String, Queue<Request>> mWaitingRequests =
new HashMap<String, Queue<Request>>();
private final Set<Request> mCurrentRequests = new HashSet<Request>();
private final PriorityBlockingQueue<Request> mCacheQueue =
new PriorityBlockingQueue<Request>();
private final PriorityBlockingQueue<Request> mNetworkQueue =
new PriorityBlockingQueue<Request>();
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
private final Cache mCache;
private final Network mNetwork;
private final ResponseDelivery mDelivery;
private NetworkDispatcher[] mDispatchers;
private CacheDispatcher mCacheDispatcher;
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
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();
}
}
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
public Request add(Request 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;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
Queue<Request> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request>();
}
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.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
……
}
源碼中關(guān)鍵的點(diǎn)有3個(gè):
- 類(lèi)成員以及構(gòu)造方法勇垛,可以看出脖母,RequestQueue里面的成員其實(shí)就是NetworkDispatcher和CacheDispatcher兩個(gè)類(lèi)以及它們構(gòu)造方法所需要的成員,比如NetworkQueue闲孤,CacheQueue谆级,Network,Cache讼积,ResponseDelivery肥照,注意這里便是傳入主線(xiàn)程Handler的地方,可以說(shuō)volley框架主要的工作配置在這里進(jìn)行的勤众。
還有看RequestQueue最簡(jiǎn)單的構(gòu)造方法舆绎,可以發(fā)現(xiàn)創(chuàng)建RequestQueu對(duì)象最少需要一個(gè)Cache和一個(gè)Network就可以了,原因也不難理解决摧,volley工作中最主要的類(lèi)就是兩個(gè)Dispatcher亿蒸,而這兩個(gè)Dispatcher的核心工作類(lèi)就是Cache和Network。 - start()方法掌桩,該方法是RequestQueue的入口方法边锁,在該方法中初始化并啟動(dòng)volley中兩個(gè)主要工作類(lèi)CacheDispathcer和多個(gè)NetworkDispatcher,此時(shí)兩個(gè)工作類(lèi)便循環(huán)的從各自的隊(duì)列CacheQueue和NetworkQueue中取出Request進(jìn)行處理波岛。
- add(Request request)方法茅坛,上面說(shuō)了,兩個(gè)Dispatcher從各自隊(duì)列中取出Request執(zhí)行则拷,那么它們各自的隊(duì)列的Request是從哪里來(lái)的呢贡蓖?答案就是RequestQueue的add方法,從該方法源碼中可以看到煌茬,首先先判斷添加的Request需不需要被緩存斥铺,如果不需要?jiǎng)t將Request添加進(jìn)NetworkQueue當(dāng)中,否則便將其添加至CacheQueue當(dāng)中坛善。
注意晾蜘,在RequestQueue成員中的NetworkDispatcher是一個(gè)數(shù)組形式,即一個(gè)RequestQueue中有多個(gè)NetworkDispathcer同時(shí)在工作眠屎,每個(gè)NetworkDispatcher實(shí)際上就是一個(gè)線(xiàn)程Thread剔交,因此NetworkDispathcer數(shù)組其實(shí)就是一個(gè)線(xiàn)程池!而且該線(xiàn)程池大小為4改衩。所以不要認(rèn)為沒(méi)有使用ThreadPoolExecutor就是沒(méi)有用線(xiàn)程池了岖常。
以上就是RequestQueue的工作流程,是不是很簡(jiǎn)單葫督,就是啟動(dòng)兩個(gè)主要工作流程Dispatcher竭鞍,然后通過(guò)add方法將Request添加至相應(yīng)的Queue當(dāng)中板惑。
3.8 Volley
上面幾節(jié)已經(jīng)將volley框架的整個(gè)工作流程都介紹清楚了,最后只剩下整個(gè)工作流程最開(kāi)始的入口點(diǎn)也就是Volley對(duì)象笼蛛,它的作用是在框架工作之前配置好工作所需的參數(shù)洒放,比如緩存的目錄,使用的網(wǎng)絡(luò)請(qǐng)求的引擎HttpStack類(lèi)型等等滨砍,工作比較簡(jiǎn)單往湿,我們來(lái)直接看Volley類(lèi)的源碼看它的工作是什么。
public class Volley {
/** Default on-disk cache directory. */
private static final String DEFAULT_CACHE_DIR = "volley";
/**
* Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
*
* @param context A {@link Context} to use for creating the cache dir.
* @param stack An {@link HttpStack} to use for the network, or null for default.
* @return A started {@link RequestQueue} instance.
*/
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
}
可以看到惋戏,Volley中只有newRequestQueue這么一個(gè)方法领追,在這個(gè)方法中,最主要的工作就是構(gòu)建RequestQueue并啟動(dòng)它响逢,而在RequestQueue那節(jié)可以知道绒窑,構(gòu)建RequestQueue最主要就是構(gòu)建Cache和Network對(duì)象,而構(gòu)建Cache主要就是配置緩存目錄舔亭,構(gòu)建Network就是選擇HttpStack請(qǐng)求引擎些膨。綜上所述,Volley的工作就是配置緩存目錄和HttpStack以構(gòu)建RequestQueue并啟動(dòng)它钦铺。
4 volley總結(jié)
最后在介紹完volley框架中主要的工作類(lèi)之后订雾,再來(lái)看看volley的工作流程便很清晰了。
可以看出來(lái)volley的流程主要分為3步矛洞,這里結(jié)合上面實(shí)際的類(lèi)仔細(xì)分析volley的工作流程:
- 通過(guò)Volley.newRequestQueue()創(chuàng)建RequestQueue對(duì)象洼哎,然后再RequestQueue.start()中配置并啟動(dòng)一個(gè)CacheDispatcher也就是負(fù)責(zé)緩存工作的Cache Thread,同時(shí)啟動(dòng)4個(gè)NetworkDispatcher用于負(fù)責(zé)網(wǎng)絡(luò)工作的Network Threads沼本,也就是線(xiàn)程池噩峦。
然后通過(guò)RequestQueuet通過(guò)add(Request r)添加Request請(qǐng)求到隊(duì)列中,首先是判斷該Reqeust是否應(yīng)該被緩存抽兆,如果不需要?jiǎng)t將Request添加到NetworkQueue當(dāng)中給NetworkDispatcher執(zhí)行识补,否則將Request添加進(jìn)CacheQueue通過(guò)CacheDispatcher進(jìn)行處理。 - CacheDispatcher的執(zhí)行流程為:從CacheQueue當(dāng)中不斷取出Request進(jìn)行處理辫红,取出一個(gè)Reuqest之后先通過(guò)Cache獲取對(duì)應(yīng)的響應(yīng)數(shù)據(jù)凭涂,若數(shù)據(jù)為空或者過(guò)期,則將Request添加進(jìn)NetworkQueue當(dāng)中厉熟;如果數(shù)據(jù)可用导盅,則通過(guò)request.parseNetworkResponse將數(shù)據(jù)封裝成Response類(lèi)對(duì)象较幌,最后通過(guò)ResponseDelivery.postResponse將Response傳回主線(xiàn)程處理揍瑟。
- 由上面可以看到,當(dāng)CacheDispatcher處理不了Request時(shí)便會(huì)將Request轉(zhuǎn)移到NetworkQueue中讓NetworkDispatcher處理乍炉。NetworkDispatcher的處理流程為:先從NetworkQueue中取出Request(就是在CacheDispatcher中放進(jìn)來(lái)的)绢片,然后通過(guò)Network執(zhí)行該請(qǐng)求獲取到NetworkResponse滤馍,然后通過(guò)request.parseNetworkResponse轉(zhuǎn)化為Response,然后根據(jù)需要將Response中的數(shù)據(jù)緩存到Cache當(dāng)中(也就是CacheDispatcher中的那個(gè)Cache)底循,最后也是通過(guò)ResponseDelivery將Response傳回到主線(xiàn)程處理巢株。
以上就是volley的實(shí)現(xiàn)原理。最后提一下volley中一些小細(xì)節(jié)的地方吧熙涤。
- 線(xiàn)程:緩存線(xiàn)程有1個(gè)阁苞,網(wǎng)絡(luò)線(xiàn)程有4個(gè)
- 緩存大小:5M
- 使用情景:十分頻繁的小數(shù)據(jù)的網(wǎng)絡(luò)通信
與Okhttp的關(guān)系:volley是一個(gè)封裝了處理請(qǐng)求祠挫、加載那槽、緩存、線(xiàn)程和回調(diào)等過(guò)程等舔,而Okhttp可以用來(lái)提供傳輸層的功能骚灸,也就是處理加載的功能,而且Okhttp的功能更強(qiáng)大慌植,因此可以使用Okhttp代替volley中的傳輸層功能(如用OKHttpStack代替HurlStack)甚牲,這樣可以使得volley加載功能更強(qiáng)大,所以這就是volley+Okhttp
注意事項(xiàng):
- Android 6.0之后就將apache的包從SDK中移除了蝶柿,而volley中又實(shí)用了apache的類(lèi)丈钙,因此在6.0之后使用volley時(shí),需要引入手動(dòng)添加apache的包
- 在使用第三方的網(wǎng)絡(luò)工具包比如volley或者okhttp時(shí)只锭,不管使用的是什么作為網(wǎng)絡(luò)請(qǐng)求的處理著恩,都應(yīng)該自己再進(jìn)行封裝一層,而不是直接調(diào)用第三方包里面的方法蜻展,這樣免得在修改第三方網(wǎng)絡(luò)庫(kù)的時(shí)候大改特改喉誊;如果自己封裝了一層,則主程序中幾乎不用做改動(dòng)纵顾,而只需在自己封裝那一個(gè)類(lèi)里面進(jìn)行替換