首页 Android磁盘缓存
文章
取消

Android磁盘缓存

1.概述

在上一篇文章中简单介绍了内存缓存,其核心就是LruCache这个类,我们知道它的优点就是直接可以读取内存,当然速度就会很快,但是它同时也有下面不足的地方:

  1. 手机内存空间十分有限,所以我们不能随意的设置内存缓存大小。
  2. 内存紧张时可能会优先被GC回收掉。
  3. 退出应用时就回收掉,不能离线存储数据

基于以上原因,于是就有了磁盘缓存,Android开源届Jake大神为我们提供了一种解决方案:DiskLruCache,也是现在应用的最广泛的一种,并且已经获得谷歌官方认可。

2.使用

打开缓存

通过源码我们可以看到DiskLruCache的构造方法时私有的,所以不能通过new的方法来获取到它的实例,但是作者给我们提供了一种方式来创建实例,通过调用open方法,其中的四个参数刚好与构造函数中的四个参数相吻合:

  1. directory指定缓存的目录
  2. appVersion指定应用程序的版本号
  3. valueCount指定一个key可以对应多少个缓存文件,一般情况下我们都指定这个参数为1
  4. maxSize指定我们最多可以缓存多大的字节数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    
      private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
     this.directory = directory;
     this.appVersion = appVersion;
     this.journalFile = new File(directory, JOURNAL_FILE);
     this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
     this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
     this.valueCount = valueCount;
     this.maxSize = maxSize;
      }
    
     /**
    * Opens the cache in {@code directory}, creating a cache if none exists
    * there.
    *
    * @param directory a writable directory
    * @param valueCount the number of values per cache entry. Must be positive.
    * @param maxSize the maximum number of bytes this cache should use to store
    * @throws IOException if reading or writing the cache directory fails
    */
      public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
       throws IOException {
     if (maxSize <= 0) {
       throw new IllegalArgumentException("maxSize <= 0");
     }
     if (valueCount <= 0) {
       throw new IllegalArgumentException("valueCount <= 0");
     }
    
     // If a bkp file exists, use it instead.
     File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
     if (backupFile.exists()) {
       File journalFile = new File(directory, JOURNAL_FILE);
       // If journal file also exists just delete backup file.
       if (journalFile.exists()) {
         backupFile.delete();
       } else {
         renameTo(backupFile, journalFile, false);
       }
     }
    
     // Prefer to pick up where we left off.
     DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
     if (cache.journalFile.exists()) {
       try {
         cache.readJournal();
         cache.processJournal();
         cache.journalWriter = new BufferedWriter(
             new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), Util.US_ASCII));
         return cache;
       } catch (IOException journalIsCorrupt) {
         System.out
             .println("DiskLruCache "
                 + directory
                 + " is corrupt: "
                 + journalIsCorrupt.getMessage()
                 + ", removing");
         cache.delete();
       }
     }
    
     // Create a new empty cache.
     directory.mkdirs();
     cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
     cache.rebuildJournal();
     return cache;
      }
    

    然后缓存路径为:/sdcard/Android/data//cache 这个路径下面,但这里我们通常的做法就是判断是否有SD卡,没有的话就放在内部存储里面,缓存路径为:/data/data//cache

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    public File getDiskCacheDir(Context context, String uniqueName) {  
     String cachePath;  
     if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())  
             || !Environment.isExternalStorageRemovable()) {  
         cachePath = context.getExternalCacheDir().getPath();  
     } else {  
         cachePath = context.getCacheDir().getPath();  
     }  
     return new File(cachePath + File.separator + uniqueName);  
    }  
    

    这里的uniqueName值作为我们区分不同的数据的一个唯一值,如存放图片的缓存文件夹(images),存放视频的缓存文件夹(videos),存放文本文件的缓存文件夹(txt)。 第二个参数获取版本号就很简单了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    public int getAppVersion(Context context) {  
     try {  
         PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);  
         return info.versionCode;  
     } catch (NameNotFoundException e) {  
         e.printStackTrace();  
     }  
     return 1;  
    }  
    

    第三个参数上面提到了为1,第四个参数假定我们设为10M,那么就有了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    DiskLruCache mDiskLruCache = null;  
    try {  
     File cacheDir = getDiskCacheDir(context, "images");  
     if (!cacheDir.exists()) {  
         cacheDir.mkdirs();  
     }  
     mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);  
    } catch (IOException e) {  
     e.printStackTrace();  
    }  
    

存缓存到磁盘

这点类似SharePreferences,DiskLruCache给我们提供了一个内部类Editor,它用来完成针对缓存文件的读取、储存、删除、修改等等操作:通过调用实例的edit方法传入key值来把文件缓存到磁盘,然后针对函数返回的Editor实例调用commit方法完成最后的提交。

1
2
mDiskLruCache.edit(key).commit();  
mDiskLruCache.flush();  

这里的key值我们一般使用MD5加密后的字符串来做唯一处理,当然也可以调用abort来放弃提交。

读取缓存文件

读取缓存文件通过get(key)方法来进行:

1
2
InputStream is = mDiskLruCache.get(key).getInputStream(0);
//TODO 然后再对流做进一步的处理

移除缓存

移除缓存调用remove(key)方法:

1
mDiskLruCache.remove(key);

获取缓存大小

通过size()方法返回当前缓存路径下面所有缓存数据的字节数,以byte为单位。

关闭缓存

调用close()方法,通常在界面销毁的时候调用

删除缓存

调用delete()方法,即可清除缓存

本文由作者按照 CC BY 4.0 进行授权

Android内存缓存

RxJava Essentials翻译总结