DiskLruCache 是一个得到官方推荐的第三方缓存工具, 它将数据缓存到了文件系统即磁盘中, 一般会用于缓存图片/视频等比较大的文件等.
DiskLruCache 在OkHttp/Glide等库里均有使用, 但是不推荐直接依赖其他库, 可以去Oracle官方下载, 或者使用JakeWharton在github的开源库: https://github.com/JakeWharton/DiskLruCache
下面翻译下官方注释, 对其特性有个大体了解.
A cache that uses a bounded amount of space on a filesystem. Each cache
entry has a string key and a fixed number of values. Each key must match
the regex [a-z0-9_-]{1,120}. Values are byte sequences,
accessible as streams or files. Each value must be between {@code 0} and
{@code Integer.MAX_VALUE} bytes in length.
一种在文件系统中使用的有界缓存。每个缓存条目都有一个字符串键和固定数量的值(注: 一般设
为1即可)。每个键(也就是每个未编码前的文件名)必须匹配正则表达式[a-z0-9_-]{1,120}。值
是字节序列,可作为流或文件访问, 其长度必须在0与Integer.MAX_VALUE字节之间。
The cache stores its data in a directory on the filesystem. This
directory must be exclusive to the cache; the cache may delete or overwrite
files from its directory. It is an error for multiple processes to use the
same cache directory at the same time.
缓存将其数据存储在文件系统的目录中。该目录必须是缓存专用的, 缓存可以从其目录中删除或覆盖文件。
多个进程同时使用相同的缓存目录将会产生错误。
This cache limits the number of bytes that it will store on the
filesystem. When the number of stored bytes exceeds the limit, the cache will
remove entries in the background until the limit is satisfied. The limit is
not strict: the cache may temporarily exceed it while waiting for files to be
deleted. The limit does not include filesystem overhead or the cache
journal so space-sensitive applications should set a conservative limit.
此缓存限制了它将存储在文件系统上的字节数。当存储字节数超过限制时,缓存将删除后台中的条目,
直到满足限制为止。这个限制是不严格的:在等待文件被删除的过程中,缓存大小有可能暂时超限。
该限制不包括文件系统的开销或缓存日志,因此对空间要求比较严格的应用程序应该设置一个保守限制。
Clients call {@link #edit} to create or update the values of an entry. An
entry may have only one editor at one time; if a value is not available to be
edited then {@link #edit} will return null.
客户端调用edit来创建或更新条目的值。一个条目同一时间只能有一个editor, 如果某值不能被编辑
的话,edit方法将返回null。
When an entry is being created it is necessary to
supply a full set of values; the empty value should be used as a
placeholder if necessary.
当一个条目被创建时,要设置一组完整的值;如果需要的的话, 可以使用空值作为占位符。
When an entry is being edited, it is not necessary
to supply data for every value; values default to their previous
value.
当条目正在被编辑时,不必为每一个值提供数据, 这些value默认为其以前的值。
Every {@link #edit} call must be matched by a call to {@link Editor#commit}
or {@link Editor#abort}. Committing is atomic: a read observes the full set
of values as they were before or after the commit, but never a mix of values.
每次使用edit时必须跟随一次commit或者abort。commit是原子性的:每次read只会发生在commit之前
或之后, 不会出现values集合是两次commit混合的情况.
Clients call {@link #get} to read a snapshot of an entry. The read will
observe the value at the time that {@link #get} was called. Updates and
removals after the call do not impact ongoing reads.
客户端调用get来读取一个条目的快照。当get被调用时, Read会同时记录相应的value。
read调用之后的更新和删除操作不会影响正在进行的读取。
This class is tolerant of some I/O errors. If files are missing from the
filesystem, the corresponding entries will be dropped from the cache. If
an error occurs while writing a cache value, the edit will fail silently.
Callers should handle other problems by catching {@code IOException} and
responding appropriately.
此类会忽略某些I/O错误的处理。如果某些文件在文件系统中消失,则相应的条目将从缓存中删除。如果在写入缓存值时
发生错误,edit操作会抛出异常。调用方应该捕获IOException并适当地处理其他问题。
通过一个自己写的工具类介绍下用到的各个方法, 这里只缓存了一个文件: imgName.
如果有不合理的地方, 欢迎讨论.
class DiskLruCacheUtils {
companion object {
/**
* 在cache下的次级目录
*/
private const val adDir = "AD"
/**
* 这里只缓存了一个文件, 所以写死了唯一的文件名
*/
private const val imgName = "ad_picture"
/**
* 开启DiskLruCache, 从inputStream中获取缓存数据
*/
fun saveADCache(inputStream: InputStream) {
// 要求必须子线程执行
if (Looper.myLooper() == Looper.getMainLooper()) {
throw Exception("CalledFromWrongThreadException")
}
// 四个参数的含义: 缓存的目录, 版本号, valueCount(同一个key对应几个value), 缓存大小限制
// 其中版本号变更后缓存会被清空, 一般无需更改, 填1即可;
// valueCount一般也设置为1, 即每个key对应一个value
val diskLruCache = DiskLruCache.open(getADCacheDir(), 1, 1, 1024 * 1024 * 50)
// 乐观锁思想, 并发时再获取一遍
val editor = diskLruCache.edit(imgName) ?: diskLruCache.edit(imgName)
val outputStream = editor?.newOutputStream(0)
val bufferIn = BufferedInputStream(inputStream)
val bufferOut = BufferedOutputStream(outputStream)
try {
var b: Int
while (true) {
b = bufferIn.read()
if (b != -1) {
bufferOut.write(b)
} else {
break
}
}
// 输出流写完后, 要执行提交操作
editor?.commit()
} catch (e: IOException) {
// 写出失败时, 要回滚
editor?.abort()
} finally {
bufferIn.close()
bufferOut.close()
diskLruCache.flush()
}
}
/**
* 删除缓存
*/
fun deleteADCache() {
if (Looper.myLooper() == Looper.getMainLooper()) {
throw Exception("CalledFromWrongThreadException")
}
if (hasADCache()) {
val diskLruCache = DiskLruCache.open(getADCacheDir(), 1, 1, 1024 * 1024 * 50)
diskLruCache.remove(imgName)
}
}
/**
* 检测缓存是否存在
*/
fun hasADCache(): Boolean {
val diskLruCache = DiskLruCache.open(getADCacheDir(), 1, 1, 1024 * 1024 * 50)
return diskLruCache.get(imgName) != null
}
/**
* 获取缓存文件的路径
*/
fun getADCachePath(): String {
return getADCacheDir().path + File.separator + imgName + ".0"
}
/**
* 获取缓存存储的位置: data/package/cache/AD/
*/
private fun getADCacheDir(): File {
val externalStorageAvailable = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
val cachePath = if (externalStorageAvailable) App.getInstance().externalCacheDir.path else App.getInstance().cacheDir.path
return File(cachePath + File.separator + adDir + File.separator)
}
}
}