标签:
----------------------------------------------------------------------------------
转载:http://blog.csdn.net/crazy__chen/article/details/46494627
----------------------------------------------------------------------------------
从上一篇文章我们已经知道,现在要处理的问题就是CacheDispatcher和NetworkDispatcher怎么分别去缓存和网络获取数据的问题,这两个问题我分开来讲。
但是首先说明的是,这两个问题其实是有联系的,当CacheDispatcher获取不到缓存的时候,会将request放入网络请求队列,从而让NetworkDispatcher去处理它;
而当NetworkDispatcher获得数据以后,又会将数据缓存,下次CacheDispatcher就可以从缓存中获得数据了。
这篇文章,就让我们先来了解volley是怎么从缓存中获取数据的。
第一个要说明的,当然是CacheDispatcher类,这个类本质是一个线程,作用就是根据request从缓存中获取数据
我们先来看它的构造方法
-
- public CacheDispatcher(
- BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
- Cache cache, ResponseDelivery delivery) {
- mCacheQueue = cacheQueue;
- mNetworkQueue = networkQueue;
- mCache = cache;
- mDelivery = delivery;
- }
从上面的方法看出,CacheDispatcher持有缓存队列cacheQueue,目的当然是为了从队列中获取东西。
而同时持有网络队列networkQueue,目的是为了在缓存请求失败后,将request放入网络队列中。
至于响应分发器delivery是成功请求缓存以后,将响应分发给对应请求的,分发器存在的目的我已经在前面的文章中说过几次了,就是为了灵活性和在主线程更新UI(至于怎么做到,我们以后会讲)
最后是一个缓存类cache,这个cache可以看成是缓存的代表,也就是说它就是缓存,是面向对象思想的体现,至于它是怎么实现的,等下会说明
看完构造方法,我们就直奔对Thread而言,最重要的run()方法
- @Override
- public void run() {
- if (DEBUG) VolleyLog.v("start new dispatcher");
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-
-
- mCache.initialize();
-
- while (true) {
- try {
-
-
-
- final Request<?> request = mCacheQueue.take();
- request.addMarker("cache-queue-take");
-
-
- if (request.isCanceled()) {
- request.finish("cache-discard-canceled");
- continue;
- }
-
-
- Cache.Entry entry = mCache.get(request.getCacheKey());
- if (entry == null) {
- request.addMarker("cache-miss");
-
- mNetworkQueue.put(request);
- continue;
- }
-
-
- if (entry.isExpired()) {
- request.addMarker("cache-hit-expired");
- request.setCacheEntry(entry);
- mNetworkQueue.put(request);
- continue;
- }
-
-
- request.addMarker("cache-hit");
- Response<?> response = request.parseNetworkResponse(
- new NetworkResponse(entry.data, entry.responseHeaders));
- request.addMarker("cache-hit-parsed");
-
- if (!entry.refreshNeeded()) {
-
- mDelivery.postResponse(request, response);
- } else {
-
-
-
- request.addMarker("cache-hit-refresh-needed");
- request.setCacheEntry(entry);
-
-
- response.intermediate = true;
-
-
-
- mDelivery.postResponse(request, response, new Runnable() {
- @Override
- public void run() {
- try {
- mNetworkQueue.put(request);
- } catch (InterruptedException e) {
-
- }
- }
- });
- }
-
- } catch (InterruptedException e) {
-
- if (mQuit) {
- return;
- }
- continue;
- }
- }
- }
这个方法里面做了很多事情,我们按顺序看
1,从缓存请求队列中取出request
2,判断这个request已经是否被取消,如果是,调用它的finish()方法,continue
3,否则,利用Cache获得缓存,获得缓存的依据是request.getCacheKey(),也就是request的url
4,如果缓存不存在,将request放入mNetworkQueue,continue
5,否则,检查缓存是否过期,是,同样将request放入mNetworkQueue,continue
6,否则,检查是否希望更新缓存,否,组装成response交给分发器mDelivery
7,否则组装成response交给分发器mDelivery,并且将request再加入mNetworkQueue,去网络请求更新
OK,上面的过程已经说得够清楚了。让人疑惑的很重要一步,就是Cache这个类到底是怎么获取缓存数据的,下面我们就来看看Cache这个类。
这个Cache其实是一个接口(面向抽象编程的思想),而它的具体实现,我们在第一篇文章的Volley类中看到,是DiskBasedCache类。
无论如何,我们先看接口
作为接口,Cache规定了缓存初始化,存取等必须的方法让子类去继承。
比较重要的是,其内部有一个Entry静态内部类,这个类Entry可以理解成一条缓存记录,也就是说每个Entry就代表一条缓存记录。
这么一说,上面run()方法里面的代码就比较好理解了,我们就知道,为什么Cache获取的缓存,叫做Entry。
然后我们来看DiskBasedCache,从名字上知道,这个类是硬盘缓存的意思
在这里我们注意到,volley其实只提供了硬盘缓存而没有内存缓存的实现,这可以说是它的不足,也可以说它作为一个扩展性很强的框架,是留给使用者自己实现的空间。如果我们需要内存缓存,我们大可自己写一个类继承Cache接口。
在这之前,我们先来看volley是怎么实现硬盘缓存的
首先是构造函数
- public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
- mRootDirectory = rootDirectory;
- mMaxCacheSizeInBytes = maxCacheSizeInBytes;
- }
-
-
- public DiskBasedCache(File rootDirectory) {
- this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
- }
这个函数传入了两个参数,一个是指缓存根目录,一个是指缓存的最大值
存取缓存,必须有存取方法,我们先从put方法看起
- @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);
- boolean success = e.writeHeader(fos);
- if (!success) {
- fos.close();
- VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
- throw new IOException();
- }
- 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());
- }
- }
这个方法一看比较复杂,我先来说明一下主要的存储过程
1,检查要缓存的数据的长度,如果当前已经缓存的数据大小mTotalSize加上要缓存的数据大小,大于缓存最大值mMaxCacheSizeInBytes,则要将旧的缓存文件删除,以腾出空间来存储新的缓存文件
2,根据缓存记录类Entry,提取Entry除了数据以外的其他信息,例如这个缓存的大小,过期时间,写入日期等,并且将这些信息实例成CacheHeader,。这样做的目的是,方便以后我们查询缓存,获得缓存相应信息时,不需要去读取硬盘,因为CacheHeader是内存中的。
3,写入缓存
根据上面步奏,我们来读pruneIfNeeded()方法,这个方法就是完成了步奏1的工作,主要思路是不断删除文件,直到腾出足够的空间给新的缓存文件
- 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);
- }
- }
在这个方法中,我们注意到有一个mEntries,我们看一下它的声明
- private final Map<String, CacheHeader> mEntries =
- new LinkedHashMap<String, CacheHeader>(16, .75f, true);
也就是说它实则保存了所有缓存的头信息CacheHeader,而且在map中,这些信息是按照LRU算法排列的,LRU算法是LinkedHashMap的内置算法。
每次存取缓存,都会修改这个map,也就是说要调用LRU算法进行重新排序,这样造成一定效率的下降,但貌似也没有更好的方法。
然后就是第二步,根据Entry生成CacheHeader,我们来看一下CacheHeader这个内部类
-
- static class CacheHeader {
-
- public long size;
-
-
- public String key;
-
-
- public String etag;
-
-
- public long serverDate;
-
-
- public long lastModified;
-
-
- public long ttl;
-
-
- public long softTtl;
-
-
- public Map<String, String> responseHeaders;
-
- private CacheHeader() { }
-
-
- public CacheHeader(String key, Entry entry) {
- this.key = key;
- this.size = entry.data.length;
- this.etag = entry.etag;
- this.serverDate = entry.serverDate;
- this.lastModified = entry.lastModified;
- this.ttl = entry.ttl;
- this.softTtl = entry.softTtl;
- this.responseHeaders = entry.responseHeaders;
- }
-
-
- public static CacheHeader readHeader(InputStream is) throws IOException {
- CacheHeader entry = new CacheHeader();
- int magic = readInt(is);
- if (magic != CACHE_MAGIC) {
-
- throw new IOException();
- }
- entry.key = readString(is);
- 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);
- entry.responseHeaders = readStringStringMap(is);
-
- return entry;
- }
-
-
- public Entry toCacheEntry(byte[] data) {
- Entry e = new Entry();
- e.data = data;
- e.etag = etag;
- e.serverDate = serverDate;
- e.lastModified = lastModified;
- e.ttl = ttl;
- e.softTtl = softTtl;
- e.responseHeaders = responseHeaders;
- return e;
- }
-
-
-
- public boolean writeHeader(OutputStream os) {
- try {
- writeInt(os, CACHE_MAGIC);
- writeString(os, key);
- writeString(os, etag == null ? "" : etag);
- writeLong(os, serverDate);
- writeLong(os, lastModified);
- writeLong(os, ttl);
- writeLong(os, softTtl);
- writeStringStringMap(responseHeaders, os);
- os.flush();
- return true;
- } catch (IOException e) {
- VolleyLog.d("%s", e.toString());
- return false;
- }
- }
-
- }
应该说没有什么特别的,其实就是把Entry类里面的,出来data以外的信息提取出来而已。
另外还增加了两个读写方法,readHeader(InputStream is)和writeHeader(OutputStream os)
从这两个方法可以知道,对于一个缓存文件来说,前面是关于这个缓存的一些信息,然后才是真正的缓存数据。
最后一步,写入缓存数据,将CacheHeader添加到map
- fos.write(entry.data);
- fos.close();
- putEntry(key, e);
OK,到此为止,写入就完成了。那么读取,就是写入的逆过程而已。
- @Override
- public synchronized Entry get(String key) {
- CacheHeader entry = mEntries.get(key);
-
- if (entry == null) {
- return null;
- }
-
- File file = getFileForKey(key);
- CountingInputStream cis = null;
- try {
- cis = new CountingInputStream(new FileInputStream(file));
- CacheHeader.readHeader(cis);
- 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;
- } catch (NegativeArraySizeException 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;
- }
- }
- }
- }
读取过程很简单
1,读取缓存文件头部
2,读取缓存文件数据
3,生成Entry,返回
相信大家都可以看懂,因为真的没有那么复杂,我就不再累述了。
get(),put()方法看过以后,其实DiskBasedCache类还有一些public方法,例如缓存信息map的初始化,例如删除所有缓存文件的方法,这些都比较简单,基本上就是利用get,put方法里面的函数就可以完成,我也不再贴出代码来说明了。
DiskBasedCache给大家讲解完毕,整个从缓存中获取数据的过程,相信也说得很清楚。
volley介绍05
标签:
原文地址:http://www.cnblogs.com/aprz512/p/5316724.html