ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android 优化 - 磁盘缓存 DiskLruCache

Android 优化 - 磁盘缓存 DiskLruCache 一、概念用于实现存储设备缓存即磁盘缓存通过将缓存对象写入文件系统从而实现缓存的效果。二、使用2.1 添加依赖最新依赖implementation com.jakewharton:disklrucache:2.0.22.2 配置权限uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAGE / uses-permission android:nameandroid.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可以对应多个valuevalue类型为字节数组大小在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 函数封装fetchData() 中对网络异常的处理详见。suspend fun T fetchData( request: suspend () - ApiResponseT ) 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 () - ApiResponseT, ) if (NetUtils.checkNetWork(APP.context)) { fetchData(request request).onSuccess { //写入DISK缓存 DiskCache.writeObj(cacheName, it as Any) } } else { val obj DiskCache.readObjT(cacheName) if (obj ! null) { //使用DISK缓存 Result.success(obj) } else { //无网络且无DISK缓存 Result.failure(Exception(无网络连接请联网后重试)) } }
返回列表