Android 缓存浅谈(二) DiskLruCache

    上篇文章讲解了使用LruCache策略在内存中缓存图片,如果你还未了解,请先看Android 缓存浅谈(一) LruCache

     在Android应用开发中,为了提高UI的流畅性、响应速度,提供更高的用户体验,开发者常常会绞尽脑汁地思考如何实现高效加载图片,而DiskLruCache实现正是开发者常用的图片缓存技术之一。Disk LRU Cache,顾名思义,硬件缓存,就是一个在文件系统中使用有限空间进行高速缓存。每个缓存项都有一个字符串键和一个固定大小的值。

     今天这篇文章 是讲解使用DiskLruCache做二级缓存。DiskLruCache是用来做磁盘缓存的良药。关于更加仔细的描述,请看郭神的这篇文章,Android DiskLruCache完全解析,硬盘缓存的最佳方案。本篇文章是讲解自己对DiskLruCache的认识。

一、下载DiskLruCache。

      DiskLruCache虽然得到了Google认证,但是SDK中还没有加入,所以我们使用DiskLruCache需要自己去下载,下载地址(该地址需要FQ,并且经常打不开),因此,我们可以在JakeWharton/DiskLruCache下载DiskLruCache源码(DiskLruCache是JakeWharton大神的杰作),另外,我提供了jar包,jar包下载地址

二、DiskLruCache使用。

2.1、创建DiskLruCache。

       DiskLruCache不能通过构造方法来创建,它通过open方法来穿件自身。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)  

 

参数说明:

(1). File directory 指定数据的缓存地址。获取路径详见2.1.1。

(2). int appVersion 指定当前应用程序的版本号。当appVersion改变时,之前的缓存都会被清除,所以如非必要,我们为其指定一个1。获取应用程序的版本号详见2.1.2。

(3). int valueCount 是Key所对应的文件数,我们通常选择一一对应的简单关系,这样比较方便控制,当然我们也可以一对多的关系,通常写入1,表示一一对应的关系。

(4). long maxSize 缓存大小。

2.1.1、设置DiskLruCache的存储路径。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public static File getDiskCacheDir(Context context, String uniqueName) {  
  2.         String cachePath;  
  3.         if (Environment.MEDIA_MOUNTED.equals(Environment  
  4.                 .getExternalStorageState())  
  5.                 || !Environment.isExternalStorageRemovable()) {  
  6.             cachePath = context.getExternalCacheDir().getPath();  
  7.         } else {  
  8.             cachePath = context.getCacheDir().getPath();  
  9.         }  
  10.         return new File(cachePath + File.separator + uniqueName);  
  11.     }  

PS:

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. Context.getExternalCacheDir().getPath(); 获取到的是 /sdcard/Android/data/<application package>/cache 这个路径   
  2. Context.getCacheDir().getPath();  获取到的是 /data/data/<application package>/cache 这个路径   

建议尽可能将缓存数据保存到/sdcard/Android/data/<application package>/cache 这个路径,好处是,当应用卸载时,该目录底下的数据会清空。更多 有关存储路径,可以查看这篇文章, Android File(一) 存储以及File操作介绍

 

2.1.2、获取应用程序的版本号。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public static int getAppVersion(Context context) {  
  2.         try {  
  3.             return context.getPackageManager().getPackageInfo(  
  4.                     context.getPackageName(), 0).versionCode;  
  5.         } catch (PackageManager.NameNotFoundException e) {  
  6.             e.printStackTrace();  
  7.         }  
  8.         return 1;  
  9.     }  

2.2、添加到缓存。

 

      将图片缓存到磁盘,需要使用DiskLruCache.Editor对象。Editor 表示一个缓存对象的编辑对象。同样Editor也不是new出来的,是通过DiskLruCache获取的。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public DiskLruCache.Editor edit(String key) throws IOException {  
  2.        return this.edit(key, -1L);  
  3.    }  

 

      首先获取图片 url 所对应的 key,然后根据 key 通过 editor() 来获取 Editor 对象,如果这个缓存正在被编辑,那么 editor()会返回 null,即 DiskLruCache 不允许同时编辑一个缓存对象。

      对于每一个存储资源都需要有一个key,这个key要是唯一的,并且和数据一一对应。现在我们存放的是图片,自然会想到了图片独一无二的url。图片Url路径,可能包含一些特殊字符,不符合文件名的标准,无法直接命名为文件名,如果将给这个url进行编码,让其比较规整,推荐使用MD5编码。下面展示 MD5代码,(该代码是网上的一段示例代码)

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public static String hashKeyForDisk(String key) {  
  2.         String cacheKey;  
  3.         try {  
  4.             final MessageDigest mDigest = MessageDigest.getInstance("MD5");  
  5.             mDigest.update(key.getBytes());  
  6.             cacheKey = bytesToHexString(mDigest.digest());  
  7.         } catch (NoSuchAlgorithmException e) {  
  8.             cacheKey = String.valueOf(key.hashCode());  
  9.         }  
  10.         return cacheKey;  
  11.     }  
  12.   
  13.     private static String bytesToHexString(byte[] bytes) {  
  14.         StringBuilder sb = new StringBuilder();  
  15.         for (int i = 0; i < bytes.length; i++) {  
  16.             String hex = Integer.toHexString(0xFF & bytes[i]);  
  17.             if (hex.length() == 1) {  
  18.                 sb.append('0');  
  19.             }  
  20.             sb.append(hex);  
  21.         }  
  22.         return sb.toString();  
  23.     }  

下面,看看如何编写具体的写入缓存代码,(该代码是网上的代码片段)

 

首先看看addBitmapToDiskLruCache()方法,

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 该代码需要在子线程中进行 将图片添加到磁盘缓存 
  3.      * @param imageUrl 图片的下载地址 
  4.      * @param diskLruCache 缓存对象 
  5.      * @return 
  6.      */  
  7.     public static boolean addBitmapToDiskLruCache(String imageUrl,  
  8.             DiskLruCache diskLruCache) {  
  9.         boolean result = false;  
  10.         String key = Md5Utils.hashKeyForDisk(imageUrl); // 通过md5加密了这个URL,生成一个key  
  11.         try {  
  12.             Editor editor = diskLruCache.edit(key);// 产生一个editor对象  
  13.             if (editor != null) {  
  14.                 // 创建一个新的输出流 ,创建DiskLruCache时设置一个节点只有一个数据,所以这里的index直接设为0即可  
  15.                 OutputStream outputStream = editor.newOutputStream(0);  
  16.                 // 通过地址获取图片数据写入到输出流  
  17.                 if (DownLoadBitmapUtils.downloadUrlToStream(imageUrl,  
  18.                         outputStream)) {  
  19.                     // 写入成功,提交  
  20.                     editor.commit();  
  21.                     result = true;  
  22.                 } else {  
  23.                     // 写入失败,中止  
  24.                     editor.abort();  
  25.                     result = false;  
  26.                 }  
  27.             }  
  28.         } catch (IOException e) {  
  29.             // TODO Auto-generated catch block  
  30.             e.printStackTrace();  
  31.         }  
  32.          return result;  
  33.     }  

首先把传递参数即图片Url,通过Md5生成一个key,然后得到一个editor对象,接着获取editor对象的输出流,通过地址获取图片数据写入到该输出流,如果返回‘true’,则调用editor.commit();提交,否则,调用editor.abort();中止此次操作。

接下来看看downloadUrlToStream()方法,

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 建立HTTP请求,并获取图片流对象。 
  3.      * @param urlString 图片下载路径 
  4.      * @param outputStream  Editor的输出流 
  5.      * @return 
  6.      */  
  7.     public static boolean downloadUrlToStream(String urlString,  
  8.             OutputStream outputStream) {  
  9.         HttpURLConnection urlConnection = null;  
  10.         BufferedOutputStream out = null;  
  11.         BufferedInputStream in = null;  
  12.         try {  
  13.             final URL url = new URL(urlString);  
  14.             urlConnection = (HttpURLConnection) url.openConnection();  
  15.             in = new BufferedInputStream(urlConnection.getInputStream(),  
  16.                     8 * 1024);  
  17.             out = new BufferedOutputStream(outputStream, 8 * 1024);  
  18.             int b;  
  19.             while ((b = in.read()) != -1) {  
  20.                 out.write(b);  
  21.             }  
  22.             return true;  
  23.         } catch (final IOException e) {  
  24.             e.printStackTrace();  
  25.         } finally {  
  26.             if (urlConnection != null) {  
  27.                 urlConnection.disconnect();  
  28.             }  
  29.             try {  
  30.                 if (out != null) {  
  31.                     out.close();  
  32.                 }  
  33.                 if (in != null) {  
  34.                     in.close();  
  35.                 }  
  36.             } catch (final IOException e) {  
  37.                 e.printStackTrace();  
  38.             }  
  39.         }  
  40.         return false;  
  41.     }  

获取图片的流写入到参数输出流中。代码都有注释,多看几遍,相信就能理解。

 

2.3、读取缓存。

     取出磁盘缓存需要使用到Snapshot对象。同样Snapshot 也不是new出来的,是通过DiskLruCache获取的。

     首先需要将URL转换成Key,然后通过DiskLruCache的get方法得到一个Snapshot对象,在通过Snapshot对象得到缓存文件的输入流,再把输入流转换成Bitamp对象。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public synchronized DiskLruCache.Snapshot get(String key) throws IOException  

 

具体代码如下所示,

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /**该代码需要在子线程中进行 
  2.      * 从缓存中获取Bitmap对象 
  3.      *  
  4.      * @param imageUrl 
  5.      * @return 
  6.      */  
  7.     public static Bitmap getCacheBitmap(String imageUrl,DiskLruCache diskLruCache) {  
  8.         String key = Md5Utils.hashKeyForDisk(imageUrl);// 把Url转换成KEY  
  9.         try {  
  10.             DiskLruCache.Snapshot snapShot = diskLruCache.get(key);// 通过key获取Snapshot对象  
  11.             if (snapShot != null) {  
  12.                 InputStream is = snapShot.getInputStream(0);// 通过Snapshot对象获取缓存文件的输入流  
  13.                 Bitmap bitmap = BitmapFactory.decodeStream(is);// 把输入流转换成Bitmap对象  
  14.                 return bitmap;  
  15.             }  
  16.         } catch (IOException e) {  
  17.             e.printStackTrace();  
  18.         }  
  19.         return null;  
  20.     }  

 

读取的时候我们最先拿到的是一个Snapshot 对象,再根据我们之前传入的参数0拿到缓存文件的流,最后把流转换为图片。到这里大家可能就明白了,之前的editor.newOutputStream(0);方法为什么会有一个0的参数了,相当于一个标识,读取时也传入参数0才能读到我们想要的数据。(假如我们的key与缓存文件不是一一对应,也就是我们一开始的open方法中传入的不是valueCount的值不是 1,那么一个key对应多个缓存文件我们要怎么区分?就是通过这种方式,有兴趣的同学查看源码就一目了然了)。

2.4、DiskLruCache的其他常用方法。

(1). size(),获取缓存大小。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 获取缓存大小 
  3.      * @return 
  4.      */  
  5.     public long getDiskLruCacheSize(DiskLruCache diskLruCache){  
  6.         return diskLruCache.size();  
  7.     }  

 

这个方法会返回当前缓存路径下所有缓存数据的总字节数,以byte为单位。

(2). remove()和delete(), 清除缓存。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 清除某一个缓存 
  3.      *  
  4.      * @param diskLruCache 
  5.      * @return 
  6.      */  
  7.     public void clearDiskLruCacheBykey(DiskLruCache diskLruCache,  
  8.             String imageUrl) {  
  9.         try {  
  10.             String key = Md5Utils.hashKeyForDisk(imageUrl);  
  11.             diskLruCache.remove(key);  
  12.         } catch (IOException e) {  
  13.             // TODO Auto-generated catch block  
  14.             e.printStackTrace();  
  15.         }  
  16.     }  
  17.   
  18.     /** 
  19.      * 清除所有缓存 
  20.      *  
  21.      * @param diskLruCache 
  22.      * @return 
  23.      */  
  24.     public void clearAllDiskLruCache(DiskLruCache diskLruCache) {  
  25.         try {  
  26.             diskLruCache.delete();  
  27.         } catch (IOException e) {  
  28.             // TODO Auto-generated catch block  
  29.             e.printStackTrace();  
  30.         }  
  31.     }  

 

(3). flush(), 这个方法用于将内存中的操作记录同步到日志文件(也就是journal文件)当中。这个方法非常重要,因为DiskLruCache能够正常工作的前提就是要依赖于journal文件中的内容。前面在讲解写入缓存操作的时候有调用过一次这个方法,但其实并不是每次写入缓存都要调用一次flush()方法的,频繁地调用并不会带来任何好处,只会额外增加同步journal文件的时间。比较标准的做法就是在Activity的onPause()方法中去调用一次flush()方法就可以了。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 并不是每次写入缓存都要调用一次flush()方法的,频繁地调用并不会带来任何好处,只会额外增加同步journal文件的时间, 
  3.      * 推荐在activity的onPause中调用 
  4.      * @param diskLruCache 
  5.      */  
  6.     public void flushDiskLruCache(DiskLruCache diskLruCache) {  
  7.         try {  
  8.             diskLruCache.flush();  
  9.         } catch (IOException e) {  
  10.             // TODO Auto-generated catch block  
  11.             e.printStackTrace();  
  12.         }  
  13.     }  

 

(4). close(),关闭DiskLruCache。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 这个方法用于将DiskLruCache关闭掉,是和open()方法对应的一个方法 
  3.      * @param diskLruCache 
  4.      */  
  5.     public void closeDiskLruCache(DiskLruCache diskLruCache) {  
  6.         try {  
  7.             diskLruCache.close();  
  8.         } catch (IOException e) {  
  9.             // TODO Auto-generated catch block  
  10.             e.printStackTrace();  
  11.         }  
  12.     }  

这个方法用于将DiskLruCache关闭掉,是和open()方法对应的一个方法。关闭掉了之后就不能再调用DiskLruCache中任何操作缓存数据的方法,通常只应该在Activity的onDestroy()方法中去调用close()方法。

三、实战。

1.新建Android项目,新建布局文件等等。

以上这几步,更加详细的可以参考 Android 缓存浅谈(一) LruCache

2. 实现DiskLruCache功能。

这是工程的包以及类截图,重点说明DiskLruCacheUtils类,下面看该类的具体代码,

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package cn.xinxing.test.utils;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Bitmap;  
  5. import android.os.Handler;  
  6. import android.os.Looper;  
  7. import android.os.Message;  
  8. import android.util.Log;  
  9. import android.widget.ImageView;  
  10. import android.widget.ListView;  
  11.   
  12. import com.jakewharton.disklrucache.DiskLruCache;  
  13.   
  14. import java.io.File;  
  15. import java.io.IOException;  
  16. import java.util.concurrent.ExecutorService;  
  17. import java.util.concurrent.Executors;  
  18. import java.util.concurrent.Future;  
  19.   
  20. import cn.xinxing.test.R;  
  21. import cn.xinxing.test.constant.Images;  
  22. import cn.xinxing.test.model.LoaderResult;  
  23.   
  24. /** 
  25.  * 磁盘缓存工具类 
  26.  */  
  27. public class DiskLruCacheUtils {  
  28.   
  29.   
  30.     private DiskLruCache mDiskLruCache;  
  31.     private static final int DISK_CACHE_SIZE = 1024 * 1024 * 50; // 磁盘缓存的大小为50M  
  32.     private static final String DISK_CACHE_SUBDIR = "bitmap"; // 设置缓存的文件名bitmap  
  33.     private static final int APP_VERSION = 1;  
  34.     private static final int VALUES_COUNT = 1;  
  35.   
  36.     private static final int CPU_COUNT = Runtime.getRuntime()  
  37.             .availableProcessors();  
  38.     private static final int CORE_POOL_SIZE = CPU_COUNT + 1;// 核心线程数  
  39.     private ExecutorService pool;// 线程池  
  40.     private Future future;  
  41.     private static final String TAG = "DiskLruCacheUtils";  
  42.     public static final int MESSAGE_POST_RESULT = 1;  
  43.     private ListView mListView;// ListView的实例  
  44.     private Handler mMainHandler = new Handler(Looper.getMainLooper()) {  
  45.         @Override  
  46.         public void handleMessage(Message msg) {  
  47.             LoaderResult result = (LoaderResult) msg.obj;  
  48.             ImageView imageView = result.imageView;  
  49.             String uri = (String) imageView.getTag();  
  50.             if (uri.equals(result.uri)) {  
  51.                 imageView.setImageBitmap(result.bitmap);  
  52.             } else {  
  53.                 imageView.setImageResource(R.mipmap.ic_launcher);  
  54.             }  
  55.         }  
  56.   
  57.         ;  
  58.     };  
  59.   
  60.   
  61.     public DiskLruCacheUtils(Context context, ListView listView) {  
  62.         mListView = listView;  
  63.         pool = Executors.newFixedThreadPool(CORE_POOL_SIZE);  
  64.         initDiskLruCache(context);  
  65.     }  
  66.   
  67.     /** 
  68.      * 初始化 
  69.      * @param context 
  70.      */  
  71.     public void initDiskLruCache(Context context) {  
  72.         try {  
  73.             File cacheDir = FileUtils.getDiskCacheDir(context, DISK_CACHE_SUBDIR);  
  74.             if (!cacheDir.exists()) {  
  75.                 cacheDir.mkdirs();  
  76.             }  
  77.             mDiskLruCache = DiskLruCache.open(cacheDir, APP_VERSION, VALUES_COUNT, DISK_CACHE_SIZE);  
  78.         } catch (IOException e) {  
  79.             e.printStackTrace();  
  80.         }  
  81.     }  
  82.   
  83.     /** 
  84.      * 显示图片 
  85.      * 
  86.      * @param imageView 
  87.      * @param imageUrl 
  88.      */  
  89.     public void showImage(final ImageView imageView, final String imageUrl) {  
  90.         imageView.setTag(imageUrl);  
  91.         // 从缓存中获取  
  92.         Runnable loadBitmapTask = new Runnable() {  
  93.   
  94.             @Override  
  95.             public void run() {  
  96.                 if (!Thread.currentThread().isInterrupted()) {  
  97.                     Bitmap bitmap = loadBitmapFromDishLruCache(imageUrl);  
  98.                     if (bitmap != null) {  
  99.                         LoaderResult result = new LoaderResult(imageView, imageUrl,  
  100.                                 bitmap);  
  101.                         mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result)  
  102.                                 .sendToTarget();  
  103.                     }  
  104.                 }  
  105.             }  
  106.         };  
  107.         future = pool.submit(loadBitmapTask);  
  108.     }  
  109.   
  110.     /** 
  111.      * 加载Bitmap对象。 
  112.      * 
  113.      * @param start 第一个可见的ImageView的下标 
  114.      * @param end   最后一个可见的ImageView的下标 
  115.      */  
  116.     public void showIamges(int start, int end) {  
  117.         for (int i = start; i < end; i++) {  
  118.             String imageUrl = Images.imageUrls[i];  
  119.             //从缓存中取图片  
  120.             ImageView imageView = (ImageView) mListView.findViewWithTag(imageUrl);  
  121.             loadImage(imageUrl, imageView);  
  122.         }  
  123.     }  
  124.   
  125.     /** 
  126.      * 加载图片 
  127.      * @param imageUrl 图片的下载路径 
  128.      * @param imageView 
  129.      */  
  130.     public void loadImage(final String imageUrl, final ImageView imageView) {  
  131.         imageView.setTag(imageUrl);  
  132.         // 从缓存中获取  
  133.         Runnable loadBitmapTask = new Runnable() {  
  134.   
  135.             @Override  
  136.             public void run() {  
  137.                 if (!Thread.currentThread().isInterrupted()) {  
  138.                     Bitmap bitmap = loadBitmap(imageUrl);  
  139.                     if (bitmap != null) {  
  140.                         LoaderResult result = new LoaderResult(imageView, imageUrl,  
  141.                                 bitmap);  
  142.                         mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result)  
  143.                                 .sendToTarget();  
  144.                     }  
  145.                     Log.e(TAG,"---->Thread run");  
  146.                 }  
  147.             }  
  148.         };  
  149.         future = pool.submit(loadBitmapTask);  
  150.     }  
  151.   
  152.     /** 
  153.      * 取消所有任务 
  154.      */  
  155.     public void cancelAllTask() {  
  156.         future.cancel(true);  
  157.     }  
  158.   
  159.     /** 
  160.      * 从缓存中加载图片 
  161.      * @param imageUrl 
  162.      * @return 
  163.      */  
  164.     private Bitmap loadBitmapFromDishLruCache(String imageUrl) {  
  165.         Bitmap bitmap;  
  166.         //从缓存中获取  
  167.         bitmap = BitmapCacheUtils.getCacheBitmap(imageUrl, mDiskLruCache);  
  168.         return bitmap;  
  169.     }  
  170.   
  171.     /** 
  172.      * 获取图片 
  173.      * @param imageUrl 
  174.      * @return 
  175.      */  
  176.     private Bitmap loadBitmap(String imageUrl) {  
  177.         Bitmap bitmap;  
  178.         //从缓存中获取  
  179.         bitmap = BitmapCacheUtils.getCacheBitmap(imageUrl, mDiskLruCache);  
  180.         if (bitmap != null) {  
  181.             return bitmap;  
  182.         }  
  183.         try {  
  184.             bitmap = loadBitmapFromHttp(imageUrl);  
  185.         } catch (IOException e) {  
  186.             // TODO Auto-generated catch block  
  187.             e.printStackTrace();  
  188.         }  
  189.         return bitmap;  
  190.     }  
  191.   
  192.   
  193.     /** 
  194.      * 磁盘缓存的添加 
  195.      * 
  196.      * @param imageUrl 
  197.      * @return 
  198.      * @throws IOException 
  199.      */  
  200.     private Bitmap loadBitmapFromHttp(String imageUrl) throws IOException {  
  201.         if (Looper.myLooper() == Looper.getMainLooper()) {  
  202.             throw new RuntimeException("can not visit network from UI Thread.");  
  203.         }  
  204.         if (mDiskLruCache == null) {  
  205.             return null;  
  206.         }  
  207.         if (BitmapCacheUtils.addBitmapToDiskLruCache(imageUrl, mDiskLruCache)) {  
  208.             return BitmapCacheUtils.getCacheBitmap(imageUrl, mDiskLruCache);  
  209.         }  
  210.         return null;  
  211.     }  
  212. }  

首先初始化,创建缓存目录以及线程池,然后加载图片时,先从缓存中获取(要在子线程中进行),如果缓存中有,则显示图片,如果没有则去下载并加入到缓存中,然后从缓存中获取,再显示。

 

使用DiskLruCacheUtils时,使用了线程池机制,因为在列表中可能会同时加载多个图片,如果只是一直创建线程,那么对app的性能以及体验都是考验,所以,建议使用线程池机制。

有关线程池,请参考这篇文章Android(线程二) 线程池详解 。

PS:代码下载连接!
总结:

     DiskLruCache的使用比LruCache稍微复杂一点,但是这一点都不影响它的性能。目前市面上大部分涉及缓存的App以及开源项目例如Android-Universal-Image-Loader,都有DiskLruCache的影子,所以值得推荐。本篇中的大部分代码都是从网上直接复制的,不要发明重复的轮子。偷笑

PS: 参考文章:   详细解读DiskLruCache

posted @ 2016-11-30 17:20  天涯海角路  阅读(224)  评论(0)    收藏  举报