一、概念
用于实现存储设备缓存,即磁盘缓存,通过将缓存对象写入文件系统从而实现缓存的效果。
二、使用
2.1 添加依赖
implementation "com.jakewharton:disklrucache:2.0.2"
2.2 配置权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
2.3 创建对象 open()
| public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) | |
| directory | 存储路径,该文件夹里面所有文件参与计算大小。 |
| appVersion | 应用的版本号,一般设为 1 即可。当版本号发生改变时会清空之前所有的缓存文件,而这个特性在实际开发中作用并不大,很多情况下即使应用的版本号发生了改变缓存文件却仍然是有效的。 |
| valueCount | 表示同一个 key 可以对应多少个缓存文件,一般设为 1 即可。 |
| maxSize | 表示缓存的总大小,比如 50MB,当缓存大小超出这个设定值后,DiskLruCache 会清除一些缓存从而保证总大小不大于这个设定值。 |
- 缓存的key为String类型,且必须匹配正则表达式[a-z0-9_-]{1,64}。
- 一个key可以对应多个value,value类型为字节数组,大小在0 ~ Integer.MAX_VALUE之间
- 缓存的目录必须为专用目录,因为DiskLruCache可能会删除或覆盖该目录下的文件。
- 添加缓存操作具备原子性,但多个进程不应该同时使用同一个缓存目录。
/**
* 外置路径:storage/emulated/0/Android/data/包名/cache
* 内置路径:data/data/包名/ceche
* @param cachePath 如果外置存储获取不到就从内置存储中获取,调用 context.externalCacheDir?.path ?: context.cacheDir.path
*/
class BitmapDiskCache(
private val cachePath: String
) {
private val cacheSize = (1024 * 1024 * 50).toLong() //50M
private val file = File(cachePath.plus("BitmapLru")).apply { if (!exists()) mkdirs() }
private val instance = DiskLruCache.open(file, 1, 1, cacheSize)
suspend fun write(key: String, value: Bitmap) = withContext(Dispatchers.IO) {
val safeKey = toSafeKey(key)
instance.use { cache ->
//获取编辑器
cache.edit(safeKey ).run {
//获取输出流(0表示key对应的第一个缓存文件,不能超过创建时设置的valueCount)
//实际使用时,根据写入的类型用需要的流再包装这个 OutputStream 后使用
newOutputStream(0).use { outputStream ->
//写入Bitmap
value.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
}
//提交
commit()
}
}
}
suspend fun read(key: String): Bitmap? = withContext(Dispatchers.IO) {
val safeKey = toSafeKey(key)
instance.use { cache ->
//获取快照
cache.get(safeKey ).run {
//获取输入流(0表示key对应的第一个缓存文件,不能超过创建时设置的valueCount)
//实际使用时,根据读取的类型用需要的流再包装这个 InputStream 后使用
getInputStream(0).use { inputStream ->
//读取Bitmap
BitmapFactory.decodeStream(inputStream)
}
}
}
}
suspend fun remove(key: String) = withContext(Dispatchers.IO) {
instance.use { cache ->
cache.remove(key)
}
}
companion object {
//MD5格式化成16进制的字符串,避免key有特殊字符串
private fun toSafeKey(str: String): String {
val byteArray = MessageDigest.getInstance("MD5").digest(str.toByteArray())
return BigInteger(1, byteArray).toString(16)
}
}
}
三、封装
3.1 工具类
object DiskCache {
private var cachePath: String? = null
private const val CACHE_SIZE = (1024 * 1024 * 50).toLong() //50M
private const val NOT_INIT = "DisCache 未初始化。使用前在 Application 中调用 DiskCache.init() 传入 Context 进行初始化。"
private val bitmapCacheInstance by lazy {
check(cachePath != null) { NOT_INIT }
val file = File(cachePath.plus("/BitmapDiskLRUCache")).apply { if (!exists()) mkdirs() }
DiskLruCache.open(file, 1, 1, CACHE_SIZE)
}
private val jsonCacheInstance by lazy {
check(cachePath != null) { NOT_INIT }
val file = File(cachePath.plus("/BeanDiskLRUCache")).apply { if (!exists()) mkdirs() }
DiskLruCache.open(file, 1, 1, CACHE_SIZE)
}
fun init(context: Context) {
//如果外置存储获取不到就从内置存储中获取
cachePath = context.externalCacheDir?.path ?: context.cacheDir?.path
}
suspend fun writeBitmap(key: String, value: Bitmap) = withContext(Dispatchers.IO) {
check(cachePath != null) { NOT_INIT }
val safeKey = toSafeKey(key)
bitmapCacheInstance.use { cache ->
//获取编辑器
cache.edit(safeKey).run {
//获取输出流(0表示key对应的第一个缓存文件,不能超过创建时设置的valueCount)
//实际使用时,根据写入的类型用需要的流再包装这个 OutputStream 后使用
newOutputStream(0).use { outputStream ->
//写入Bitmap
value.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
}
//提交
commit()
}
}
}
suspend fun writeObj(key: String, obj: Any) = withContext(Dispatchers.IO) {
check(cachePath != null) { NOT_INIT }
val safeKey = toSafeKey(key)
jsonCacheInstance.use { cache ->
cache.edit(safeKey).run {
ObjectOutputStream(newOutputStream(0)).use { outputStream ->
outputStream.writeObject(obj)
}
commit()
}
}
}
suspend fun readBitmap(key: String): Bitmap? = withContext(Dispatchers.IO) {
check(cachePath != null) { NOT_INIT }
val safeKey = toSafeKey(key)
bitmapCacheInstance.use { cache ->
//获取快照
cache.get(safeKey).run {
//获取输入流(0表示key对应的第一个缓存文件,不能超过创建时设置的valueCount)
//实际使用时,根据读取的类型用需要的流再包装这个 InputStream 后使用
getInputStream(0).use { inputStream ->
//读取Bitmap
BitmapFactory.decodeStream(inputStream)
}
}
}
}
suspend fun <T> readObj(key: String): T? = withContext(Dispatchers.IO) {
check(cachePath != null) { NOT_INIT }
val safeKey = toSafeKey(key)
var obj: T? = null
jsonCacheInstance.use { cache ->
cache.get(safeKey).run {
ObjectInputStream(getInputStream(0)).use { inputStream ->
obj = inputStream.readObject() as T
}
}
}
obj
}
suspend fun removeBitMap(key: String) = withContext(Dispatchers.IO) {
check(cachePath != null) { NOT_INIT }
bitmapCacheInstance.use { cache ->
cache.remove(key)
}
}
suspend fun removeObj(key: String) = withContext(Dispatchers.IO) {
check(cachePath != null) { NOT_INIT }
jsonCacheInstance.use { cache ->
cache.remove(key)
}
}
//MD5格式化成16进制的字符串,避免key有特殊字符串
private fun toSafeKey(str: String): String {
val byteArray = MessageDigest.getInstance("MD5").digest(str.toByteArray())
return BigInteger(1, byteArray).toString(16)
}
}
3.2 函数封装
suspend fun <T> fetchData(
request: suspend () -> ApiResponse<T>
) = runCatching {
request()
}.then { apiResponse ->
apiResponse.getData()
}.onFailure {
//在此对异常进行细分处理和日志记录
when (it) {
//挂起函数作为检查点,需要抛出 CancellationException 来停止后续代码的执行
is CancellationException -> throw it
}
Log.e("获取数据失败", it.message.toString())
}
/**
* 先判断是否有网络,
* 有网络就联网获取然后写入DISK缓存中
* 无网络的话,有DISK缓存就使用,无DISK缓存返回失败
*/
suspend fun <T> fetchDataWithDiskCache(
cacheName: String,
request: suspend () -> ApiResponse<T>,
) = if (NetUtils.checkNetWork(APP.context)) {
fetchData(request = request).onSuccess {
//写入DISK缓存
DiskCache.writeObj(cacheName, it as Any)
}
} else {
val obj = DiskCache.readObj<T>(cacheName)
if (obj != null) {
//使用DISK缓存
Result.success(obj)
} else {
//无网络且无DISK缓存
Result.failure(Exception("无网络连接,请联网后重试"))
}
}
本文介绍了如何在Android应用中使用DiskLruCache进行存储设备缓存,包括添加依赖、配置权限、创建对象和使用方法(写入、读取、移除),以及注意事项如缓存目录选择和并发使用限制。
847

被折叠的 条评论
为什么被折叠?



