首页 > 代码库 > 请注意,Volley已默认使用磁盘缓存
请注意,Volley已默认使用磁盘缓存
之前学习volley框架,用ImageLoader可以设置内存缓存,用一个LruCache,就可以避免OOM且图片读取速度快,爽极了。
后来想,如果只是内存缓存的话,那退出程序或者内存不够大了,缓存的图片不就被清理掉了,这样每次启动程序就又得去网上下载图片,流量好贵的。
看起来还行,运行起来,效果怎么一般般,感觉卡卡滴,不对呀,难道磁盘缓存这么慢?于是把磁盘缓存先去掉,只留下内存缓存。试了一下,流畅了不少。不过奇怪的是,这样退出程序后再进来,图片依然还在!!!
我把刚才磁盘上的缓存文件在手机上删除后,断网,再进入程序,奇怪的事情再一次发生,图片依旧可以快速加载出来!!!
WTF,见鬼了。
难道LruCache不止可以内存缓存,还可以磁盘缓存?(不对呀,LruCache不是存在LinkedHashMap里的吗,怎么可能)赶紧打开LruCache源码瞧瞧,看了半天什么也没发现。
又想难道是Volley还有磁盘缓存???
为了证实这两个想法哪个正确,我又做了一个实验,把图片缓存进Lrucache里和不设置任何缓存的Volley里,结果是:
1:Lrucache只把图片存在内存里,退出程序内存里的图片就回收掉。
2:Volley不设置缓存,退出程序关掉网络,再次进入也能加载刚才下载下来的图片。
那么问题来了,我搞了半天的磁盘缓存原来volley的默认实现就有了,真是学艺不精啊~ 这下我学乖了,看源码,只有源码才靠得住,其他的坑太多。
后来想,如果只是内存缓存的话,那退出程序或者内存不够大了,缓存的图片不就被清理掉了,这样每次启动程序就又得去网上下载图片,流量好贵的。
于是找到了磁盘缓存框架DiskLruCache,这是一个挺著名的开源框架,网易云阅读等APP之前都用它来缓存图片,关于这个框架的使用可以看这篇博客。
找到这个框架后我就着手把DiskLruCache和Volley结合起来,用LruCache做一级缓存,用DiskLruCache做二级缓存,这样做使得程序又快又不用每次都去网上下载图片浪费流量。说干咋就干,做前股沟了一下,找到了一篇关于此实现的相关介绍,原来有老外也想到了这点(介绍链接),并把他的代码再github上开源出来。(github地址)
话不多说,down下来后看了这哥们的源码,并不复杂,只不过他并没有实现我心中的二级缓存,而是用BitmapLruImageCache和DiskLruImageCache将两个缓存分开,用的时候在ImageCacheManager里设置用哪个缓存。看来还是得自己动手,不一会儿就把它们揉合在一起了,源码如下:
public class LevelTwoCache implements ImageCache { private BitmapLruImageCache bitImageCache; private DiskLruImageCache diskImageCache; public LevelTwoCache(Context context, String uniqueName, int diskCacheSize, int memCacheSize, CompressFormat compressFormat, int quality) { bitImageCache = new BitmapLruImageCache(memCacheSize); diskImageCache = new DiskLruImageCache(context, uniqueName, diskCacheSize, compressFormat, quality); } /** * 先从内存获取图片,如果内存找不到就从磁盘里找,找到了存在内存里并返回 */ @Override public Bitmap getBitmap(String url) { String key = createKey(url); Bitmap bitmap = null; if (bitImageCache.getBitmap(key) == null) { if (diskImageCache.containsKey(key)) { return null; } else { bitmap = diskImageCache.getBitmap(key); bitImageCache.putBitmap(key, bitmap); } } else { bitmap = bitImageCache.getBitmap(key); } return bitmap; } /** * 首次图片从网络下载下来后分别保存在内存和磁盘缓存里 */ @Override public void putBitmap(String url, Bitmap bitmap) { String key = createKey(url); bitImageCache.putBitmap(key, bitmap); diskImageCache.putBitmap(key, bitmap); } /** * 把url转成MD5 */ private String createKey(String url) { return getBitmapMDKey(url); } public String getBitmapMDKey(String key) { String cacheKey; try { final MessageDigest mDigest = MessageDigest.getInstance("MD5"); mDigest.update(key.getBytes()); cacheKey = bytesToHexString(mDigest.digest()); } catch (NoSuchAlgorithmException e) { cacheKey = String.valueOf(key.hashCode()); } return cacheKey; } private String bytesToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(0xFF & bytes[i]); if (hex.length() == 1) { sb.append('0'); } sb.append(hex); } return sb.toString(); } /** * 清除内存缓存 */ public void cleanMemCache() { bitImageCache.evictAll(); } /** * 清除磁盘缓存 */ public void cleanDiskCache() { diskImageCache.clearCache(); } }
看起来还行,运行起来,效果怎么一般般,感觉卡卡滴,不对呀,难道磁盘缓存这么慢?于是把磁盘缓存先去掉,只留下内存缓存。试了一下,流畅了不少。不过奇怪的是,这样退出程序后再进来,图片依然还在!!!
我把刚才磁盘上的缓存文件在手机上删除后,断网,再进入程序,奇怪的事情再一次发生,图片依旧可以快速加载出来!!!
WTF,见鬼了。
难道LruCache不止可以内存缓存,还可以磁盘缓存?(不对呀,LruCache不是存在LinkedHashMap里的吗,怎么可能)赶紧打开LruCache源码瞧瞧,看了半天什么也没发现。
又想难道是Volley还有磁盘缓存???
为了证实这两个想法哪个正确,我又做了一个实验,把图片缓存进Lrucache里和不设置任何缓存的Volley里,结果是:
1:Lrucache只把图片存在内存里,退出程序内存里的图片就回收掉。
2:Volley不设置缓存,退出程序关掉网络,再次进入也能加载刚才下载下来的图片。
那么问题来了,我搞了半天的磁盘缓存原来volley的默认实现就有了,真是学艺不精啊~ 这下我学乖了,看源码,只有源码才靠得住,其他的坑太多。
volley的源码分析,这里就不讲了,如有兴趣自己看或者结合源码看这篇博客,这里只看跟磁盘缓存相关的几段关键性代码,其中最重要的是DiskBasedCache类(里面的缓存算法也是LRU算法):
1:磁盘缓存的创建,在Volley.java的newRequestQueue方法里,随着requesQueue一起初始化 :
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) { 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; if (maxDiskCacheBytes <= -1) { // 如果你不设置磁盘缓存最大值的话这里初始化(默认是5M) queue = new RequestQueue(new DiskBasedCache(cacheDir), network); } else { // 设置了最大缓存值在这里初始化 queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network); } queue.start(); return queue;2. 把图片保存在DiskBasedCache里,是在NetworkDispatcher的run()方法里:
public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while (true) { long startTimeMs = SystemClock.elapsedRealtime(); Request<?> request; try { // Take a request from the queue. request = mQueue.take(); } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if (mQuit) { return; } continue; } try { request.addMarker("network-queue-take"); // If the request was cancelled already, do not perform the // network request. if (request.isCanceled()) { request.finish("network-discard-cancelled"); continue; } addTrafficStatsTag(request); // Perform the network request. NetworkResponse networkResponse = mNetwork.performRequest(request); request.addMarker("network-http-complete"); // If the server returned 304 AND we delivered a response already, // we're done -- don't deliver a second identical response. if (networkResponse.notModified && request.hasHadResponseDelivered()) { request.finish("not-modified"); continue; } // Parse the response here on the worker thread. Response<?> response = request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); //默认需要缓存,把response.cacheEntry保存到DiskBasedCache if (request.shouldCache() && response.cacheEntry != null) { mCache.put(request.getCacheKey(), response.cacheEntry); request.addMarker("network-cache-written"); } // Post the response back. request.markDelivered(); mDelivery.postResponse(request, response); } catch (VolleyError volleyError) { volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); parseAndDeliverNetworkError(request, volleyError); } catch (Exception e) { VolleyLog.e(e, "Unhandled exception %s", e.toString()); VolleyError volleyError = new VolleyError(e); volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); mDelivery.postError(request, volleyError); } } }DiskBasedCache里的put方法就是通过流把数据写到磁盘上的:
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()); } }3. 把图片从DiskBasedCache里取出来,是在CacheDispatcher的run()里 :
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; } //从DiskBasedCache取出bitmap数据 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; } } }DiskBasedCache的get是通过输入流把文件读取进来,然后转成CacheEntry的:
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 = http://www.mamicode.com/streamToBytes(cis, (int) (file.length() - cis.bytesRead));>
总结,Volley已经默认使用了磁盘缓存,官方推荐开发人员自己加上内存缓存,所以只需要很简单的在ImageLoader里设置内存缓存就可以实现二级缓存,不必配合上DiskLruCache了,配合DiskLruCache只会造成冗余,两次硬盘缓存,当然更慢了。
另外,开源框架用前最好过一下源码,不要像我这样搞了半天白忙活~~~~~请注意,Volley已默认使用磁盘缓存
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。