标签:android java volley 缓存 lrucache
之前学习volley框架,用ImageLoader可以设置内存缓存,用一个LruCache,就可以避免OOM且图片读取速度快,爽极了。于是找到了磁盘缓存框架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(); } }
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 = 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; } } } }
标签:android java volley 缓存 lrucache
原文地址:http://blog.csdn.net/asdzheng/article/details/43162137