Volley中的默认缓存

之前学习volley框架,用ImageLoader可以设置内存缓存,用一个LruCache,就可以避免OOM且图片读取速度快,爽极了。

后来想,如果只是内存缓存的话,那退出程序或者内存不够大了,缓存的图片不就被清理掉了,这样每次启动程序就又得去网上下载图片,流量好贵的。

于是找到了磁盘缓存框架DiskLruCache,这是一个挺著名的开源框架,网易云阅读等APP之前都用它来缓存图片,关于这个框架的使用可以看这篇博客

找到这个框架后我就着手把DiskLruCache和Volley结合起来,用LruCache做一级缓存,用DiskLruCache做二级缓存,这样做使得程序又快又不用每次都去网上下载图片浪费流量。说干咋就干,做前股沟了一下,找到了一篇关于此实现的相关介绍,原来有老外也想到了这点(介绍链接),并把他的代码再github上开源出来。(github地址

话不多说,down下来后看了这哥们的源码,并不复杂,只不过他并没有实现我心中的二级缓存,而是用BitmapLruImageCache和DiskLruImageCache将两个缓存分开,用的时候在ImageCacheManager里设置用哪个缓存。看来还是得自己动手,不一会儿就把它们揉合在一起了,源码如下:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class LevelTwoCache implements ImageCache {  
  2.   
  3.     private BitmapLruImageCache bitImageCache;  
  4.   
  5.     private DiskLruImageCache diskImageCache;  
  6.   
  7.     public LevelTwoCache(Context context, String uniqueName, int diskCacheSize, int memCacheSize,  
  8.             CompressFormat compressFormat, int quality) {  
  9.   
  10.         bitImageCache = new BitmapLruImageCache(memCacheSize);  
  11.         diskImageCache = new DiskLruImageCache(context, uniqueName, diskCacheSize, compressFormat,  
  12.                 quality);  
  13.   
  14.     }  
  15.   
  16.     /** 
  17.      * 先从内存获取图片,如果内存找不到就从磁盘里找,找到了存在内存里并返回 
  18.      */  
  19.     @Override  
  20.     public Bitmap getBitmap(String url) {  
  21.         String key = createKey(url);  
  22.         Bitmap bitmap = null;  
  23.   
  24.         if (bitImageCache.getBitmap(key) == null) {  
  25.             if (diskImageCache.containsKey(key)) {  
  26.                 return null;  
  27.             } else {  
  28.                 bitmap = diskImageCache.getBitmap(key);  
  29.                 bitImageCache.putBitmap(key, bitmap);  
  30.             }  
  31.         } else {  
  32.             bitmap = bitImageCache.getBitmap(key);  
  33.         }  
  34.   
  35.         return bitmap;  
  36.     }  
  37.   
  38.     /** 
  39.      * 首次图片从网络下载下来后分别保存在内存和磁盘缓存里 
  40.      */  
  41.     @Override  
  42.     public void putBitmap(String url, Bitmap bitmap) {  
  43.         String key = createKey(url);  
  44.   
  45.         bitImageCache.putBitmap(key, bitmap);  
  46.         diskImageCache.putBitmap(key, bitmap);  
  47.     }  
  48.   
  49.     /** 
  50.      * 把url转成MD5 
  51.      */  
  52.     private String createKey(String url) {  
  53.         return getBitmapMDKey(url);  
  54.     }  
  55.   
  56.     public String getBitmapMDKey(String key) {  
  57.         String cacheKey;  
  58.         try {  
  59.             final MessageDigest mDigest = MessageDigest.getInstance("MD5");  
  60.             mDigest.update(key.getBytes());  
  61.             cacheKey = bytesToHexString(mDigest.digest());  
  62.         } catch (NoSuchAlgorithmException e) {  
  63.             cacheKey = String.valueOf(key.hashCode());  
  64.         }  
  65.         return cacheKey;  
  66.     }  
  67.   
  68.     private String bytesToHexString(byte[] bytes) {  
  69.         StringBuilder sb = new StringBuilder();  
  70.         for (int i = 0; i < bytes.length; i++) {  
  71.             String hex = Integer.toHexString(0xFF & bytes[i]);  
  72.             if (hex.length() == 1) {  
  73.                 sb.append('0');  
  74.             }  
  75.             sb.append(hex);  
  76.         }  
  77.         return sb.toString();  
  78.     }  
  79.   
  80.     /** 
  81.      * 清除内存缓存 
  82.      */  
  83.     public void cleanMemCache() {  
  84.         bitImageCache.evictAll();  
  85.     }  
  86.   
  87.     /** 
  88.      * 清除磁盘缓存 
  89.      */  
  90.     public void cleanDiskCache() {  
  91.         diskImageCache.clearCache();  
  92.     }  
  93.   
  94. }  

看起来还行,运行起来,效果怎么一般般,感觉卡卡滴,不对呀,难道磁盘缓存这么慢?于是把磁盘缓存先去掉,只留下内存缓存。试了一下,流畅了不少。不过奇怪的是,这样退出程序后再进来,图片依然还在!!!

我把刚才磁盘上的缓存文件在手机上删除后,断网,再进入程序,奇怪的事情再一次发生,图片依旧可以快速加载出来!!!

WTF,见鬼了。

难道LruCache不止可以内存缓存,还可以磁盘缓存?(不对呀,LruCache不是存在LinkedHashMap里的吗,怎么可能)赶紧打开LruCache源码瞧瞧,看了半天什么也没发现。

又想难道是Volley还有磁盘缓存???

为了证实这两个想法哪个正确,我又做了一个实验,把图片缓存进Lrucache里和 不设置任何缓存的 Volley里,结果是:

 1:Lrucache只把图片存在内存里,退出程序内存里的图片就回收掉。
 2:Volley不设置缓存,退出程序关掉网络,再次进入也能加载刚才下载下来的图片。

 那么问题来了,我搞了半天的磁盘缓存原来volley的默认实现就有了,真是学艺不精啊~ 这下我学乖了,看源码,只有源码才靠得住,其他的坑太多。

volley的源码分析,这里就不讲了,如有兴趣自己看或者结合源码看这篇博客,这里只看跟磁盘缓存相关的几段关键性代码,其中最重要的是DiskBasedCache类(里面的缓存算法也是LRU算法):

1:磁盘缓存的创建,在Volley.java的newRequestQueue方法里,随着requesQueue一起初始化 :

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {  
  2.         File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);  
  3.   
  4.         String userAgent = "volley/0";  
  5.         try {  
  6.             String packageName = context.getPackageName();  
  7.             PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);  
  8.             userAgent = packageName + "/" + info.versionCode;  
  9.         } catch (NameNotFoundException e) {  
  10.         }  
  11.   
  12.         if (stack == null) {  
  13.             if (Build.VERSION.SDK_INT >= 9) {  
  14.                 stack = new HurlStack();  
  15.             } else {  
  16.                 // Prior to Gingerbread, HttpUrlConnection was unreliable.  
  17.                 // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html  
  18.                 stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));  
  19.             }  
  20.         }  
  21.   
  22.         Network network = new BasicNetwork(stack);  
  23.           
  24.         RequestQueue queue;  
  25.         if (maxDiskCacheBytes <= -1)  
  26.         {  
  27.                          // 如果你不设置磁盘缓存最大值的话这里初始化(默认是5M)  
  28.             queue = new RequestQueue(new DiskBasedCache(cacheDir), network);  
  29.         }  
  30.         else  
  31.         {  
  32.                          // 设置了最大缓存值在这里初始化  
  33.             queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);  
  34.         }  
  35.   
  36.         queue.start();  
  37.   
  38.         return queue;  
  39.       
2. 把图片保存在DiskBasedCache里,是在NetworkDispatcher的run()方法里:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public void run() {  
  2.     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
  3.     while (true) {  
  4.         long startTimeMs = SystemClock.elapsedRealtime();  
  5.         Request<?> request;  
  6.         try {  
  7.             // Take a request from the queue.  
  8.             request = mQueue.take();  
  9.         } catch (InterruptedException e) {  
  10.             // We may have been interrupted because it was time to quit.  
  11.             if (mQuit) {  
  12.                 return;  
  13.             }  
  14.             continue;  
  15.         }  
  16.   
  17.         try {  
  18.             request.addMarker("network-queue-take");  
  19.   
  20.             // If the request was cancelled already, do not perform the  
  21.             // network request.  
  22.             if (request.isCanceled()) {  
  23.                 request.finish("network-discard-cancelled");  
  24.                 continue;  
  25.             }  
  26.   
  27.             addTrafficStatsTag(request);  
  28.   
  29.             // Perform the network request.  
  30.             NetworkResponse networkResponse = mNetwork.performRequest(request);  
  31.             request.addMarker("network-http-complete");  
  32.   
  33.             // If the server returned 304 AND we delivered a response already,  
  34.             // we're done -- don't deliver a second identical response.  
  35.             if (networkResponse.notModified && request.hasHadResponseDelivered()) {  
  36.                 request.finish("not-modified");  
  37.                 continue;  
  38.             }  
  39.   
  40.             // Parse the response here on the worker thread.  
  41.             Response<?> response = request.parseNetworkResponse(networkResponse);  
  42.             request.addMarker("network-parse-complete");  
  43.   
  44.               
  45.             //默认需要缓存,把response.cacheEntry保存到DiskBasedCache  
  46.             if (request.shouldCache() && response.cacheEntry != null) {  
  47.                 mCache.put(request.getCacheKey(), response.cacheEntry);  
  48.                 request.addMarker("network-cache-written");  
  49.             }  
  50.   
  51.             // Post the response back.  
  52.             request.markDelivered();  
  53.             mDelivery.postResponse(request, response);  
  54.         } catch (VolleyError volleyError) {  
  55.             volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);  
  56.             parseAndDeliverNetworkError(request, volleyError);  
  57.         } catch (Exception e) {  
  58.             VolleyLog.e(e, "Unhandled exception %s", e.toString());  
  59.             VolleyError volleyError = new VolleyError(e);  
  60.             volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);  
  61.             mDelivery.postError(request, volleyError);  
  62.         }  
  63.     }  
  64. }  
DiskBasedCache里的put方法就是通过流把数据写到磁盘上的:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public synchronized void put(String key, Entry entry) {  
  2.         pruneIfNeeded(entry.data.length);  
  3.         File file = getFileForKey(key);  
  4.         try {  
  5.             FileOutputStream fos = new FileOutputStream(file);  
  6.             CacheHeader e = new CacheHeader(key, entry);  
  7.             boolean success = e.writeHeader(fos);  
  8.             if (!success) {  
  9.                 fos.close();  
  10.                 VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());  
  11.                 throw new IOException();  
  12.             }  
  13.             fos.write(entry.data);  
  14.             fos.close();  
  15.             putEntry(key, e);  
  16.             return;  
  17.         } catch (IOException e) {  
  18.         }  
  19.         boolean deleted = file.delete();  
  20.         if (!deleted) {  
  21.             VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());  
  22.         }  
  23.     }  
3. 把图片从DiskBasedCache里取出来,是在CacheDispatcher的run()里 :

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public void run() {  
  2.         if (DEBUG) VolleyLog.v("start new dispatcher");  
  3.         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
  4.   
  5.         // Make a blocking call to initialize the cache.  
  6.         mCache.initialize();  
  7.   
  8.         while (true) {  
  9.             try {  
  10.                 // Get a request from the cache triage queue, blocking until  
  11.                 // at least one is available.  
  12.                 final Request<?> request = mCacheQueue.take();  
  13.                 request.addMarker("cache-queue-take");  
  14.   
  15.                 // If the request has been canceled, don't bother dispatching it.  
  16.                 if (request.isCanceled()) {  
  17.                     request.finish("cache-discard-canceled");  
  18.                     continue;  
  19.                 }  
  20.   
  21.                 //从DiskBasedCache取出bitmap数据  
  22.                 Cache.Entry entry = mCache.get(request.getCacheKey());  
  23.   
  24.                 if (entry == null) {  
  25.                     request.addMarker("cache-miss");  
  26.                     // Cache miss; send off to the network dispatcher.  
  27.                     mNetworkQueue.put(request);  
  28.                     continue;  
  29.                 }  
  30.   
  31.                 // If it is completely expired, just send it to the network.  
  32.                 if (entry.isExpired()) {  
  33.                     request.addMarker("cache-hit-expired");  
  34.                     request.setCacheEntry(entry);  
  35.                     mNetworkQueue.put(request);  
  36.                     continue;  
  37.                 }  
  38.   
  39.                 // We have a cache hit; parse its data for delivery back to the request.  
  40.                 request.addMarker("cache-hit");  
  41.                 Response<?> response = request.parseNetworkResponse(  
  42.                         new NetworkResponse(entry.data, entry.responseHeaders));  
  43.                 request.addMarker("cache-hit-parsed");  
  44.   
  45.                 if (!entry.refreshNeeded()) {  
  46.                     // Completely unexpired cache hit. Just deliver the response.  
  47.                     mDelivery.postResponse(request, response);  
  48.                 } else {  
  49.                     // Soft-expired cache hit. We can deliver the cached response,  
  50.                     // but we need to also send the request to the network for  
  51.                     // refreshing.  
  52.                     request.addMarker("cache-hit-refresh-needed");  
  53.                     request.setCacheEntry(entry);  
  54.   
  55.                     // Mark the response as intermediate.  
  56.                     response.intermediate = true;  
  57.   
  58.                     // Post the intermediate response back to the user and have  
  59.                     // the delivery then forward the request along to the network.  
  60.                     mDelivery.postResponse(request, response, new Runnable() {  
  61.                         @Override  
  62.                         public void run() {  
  63.                             try {  
  64.                                 mNetworkQueue.put(request);  
  65.                             } catch (InterruptedException e) {  
  66.                                 // Not much we can do about this.  
  67.                             }  
  68.                         }  
  69.                     });  
  70.                 }  
  71.   
  72.             } catch (InterruptedException e) {  
  73.                 // We may have been interrupted because it was time to quit.  
  74.                 if (mQuit) {  
  75.                     return;  
  76.                 }  
  77.                 continue;  
  78.             }  
  79.         }  
  80.     }  
DiskBasedCache的get是通过输入流把文件读取进来,然后转成CacheEntry的:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public synchronized Entry get(String key) {  
  2.         CacheHeader entry = mEntries.get(key);  
  3.         // if the entry does not exist, return.  
  4.         if (entry == null) {  
  5.             return null;  
  6.         }  
  7.   
  8.         File file = getFileForKey(key);  
  9.         CountingInputStream cis = null;  
  10.         try {  
  11.             cis = new CountingInputStream(new FileInputStream(file));  
  12.             CacheHeader.readHeader(cis); // eat header  
  13.             byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));  
  14.             return entry.toCacheEntry(data);  
  15.         } catch (IOException e) {  
  16.             VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());  
  17.             remove(key);  
  18.             return null;  
  19.         } finally {  
  20.             if (cis != null) {  
  21.                 try {  
  22.                     cis.close();  
  23.                 } catch (IOException ioe) {  
  24.                     return null;  
  25.                 }  
  26.             }  
  27.         }  
  28.     }  

总结,Volley已经默认使用了磁盘缓存,官方推荐开发人员自己加上内存缓存,所以只需要很简单的在ImageLoader里设置内存缓存就可以实现二级缓存,不必配合上DiskLruCache了,配合DiskLruCache只会造成冗余,两次硬盘缓存,当然更慢了。

另外,开源框架用前最好过一下源码,不要像我这样搞了半天白忙活~~~~~
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值