当前位置:网站首页>结合Retrofit 改造OKHttp 缓存
结合Retrofit 改造OKHttp 缓存
2022-07-29 01:48:00 【小米椒……】
本篇是基于Okhttp
缓存方式改造思路的介绍 以及结合 Retrofit
来使用的方法。
网络缓存
说到网络缓存,肯定都不陌生,多多少少使用过不同的缓存方案。使用网络缓存有什么作用:
- 减少服务器请求次数
- 减少用户等待时间
- 增加应用流畅度
- 节省用户流量(虽然现在流量也不怎么值钱了)
…
OkHttp缓存
OkHttp
是自带缓存的,基于请求头的缓存,使用起来有局限性,说实话之前只是了解过,项目中并没有真正去用到过 OkHttp 自带的缓存。原因如下:
OkHttp 只缓存Get请求,其他请求会直接忽略,使用起来并不是太方便,而且内部定义的缓存的策略不是太符合业务的需求。
RxCache
RxCache 专门为Retrifit
写出来的缓存库。RxCache
看名字就知道了,Rx开头肯定是基于RxJava的库。使用方式跟Retrofit 的使用方式类似,都要创建一个Service,在Service里用注解声明不同的缓存方式,所以用起来还是挺灵活的,不过作者好像不维护的样子,上个版本是 2016 年出的了。上手难度稍微一点点高,里边那几个注解的意思不自己测试下还真不太好完全明白是什么意思。这个库不是今天介绍的重点,有兴趣的还是看下官方文档。
思路
相比于 RxCache
的缓存方式 和 OkHttp
自带的基于拦截器的缓存方式。我个人还是更看好后者,基于拦截器来做缓存。所以才有了这个想法,想改一下能更适合项目中的具体业务需求。
总体思路是:用 Retrofit
的@Header
和@Header
注解去添加 缓存策略、缓存时间、以及 缓存 Key值。拿到这些缓存信息后,然后在自定义拦截器里边取到缓存模式等信息,再把这几个请求头移除掉, 再去进行缓存的判断以及读写操作。不影响正常的网络请求。
缓存模式
首先要考虑的就是缓存策略,结合业务的需求定义出来以下几种:
- ONLY_NETWORK :只请求网络,不加缓存
- ONLY_CACHE :只读取缓存(没有缓存抛出异常)
- NETWORK_PUT_CACHE : 先请求网络,再写入缓存
- READ_CACHE_NETWORK_PUT :先读取缓存,如果缓存失效再请求网络更新缓存
- NETWORK_PUT_READ_CACHE :先请求网络,网络请求失败使用缓存 (未过期缓存)
可能第二种ONLY_CACHE
好多朋友会有疑问,只读取缓存的场景想不出来哪里会用到,这个场景用到的确实不多,有朋友经常看谷歌Demo的话,里边倒是有好多这种场景,谷歌Demo里在页面打开的时候去拿缓存先展示出来,然后再请求网络把数据更新上去。
缓存Key
缓存的Key值需要是唯一的,直接用 URL 来做缓存的Key值,GET 请求是没问题的,但是POST请求参数是放在Body 里边的,POST 请求就需要读取出来请求参数,再添加到URL里生成新的 URl 来当做 缓存Key。
private fun buildCacheKey(request: Request): String {
val requestBody = request.body ?: return request.url.toString()
val buffer = Buffer()
requestBody.writeTo(buffer)
val contentType = requestBody.contentType()
val charset = contentType?.charset(Charsets.UTF_8) ?: Charsets.UTF_8
if (isProbablyUtf8(buffer)) {
val questParam = buffer.readString(charset)
buffer.close()
if (questParam.isBlank()) return request.url.toString()
val builder = request.url.newBuilder()
kotlin.runCatching {
builder.addQueryParameter("${request.method.lowercase()}param", questParam)
return builder.build().toString()
}.onFailure {
return ""
}
}
return request.url.toString()
}
拦截器
我们在拦截器里做缓存,每次请求可能会是不同的策略,所以首先要拿到的就是缓存模式,拿到缓存模式之后再根据不同的模式去读取或者写入操作,核心代码也就下边这几行:
override fun intercept(chain: Interceptor.Chain): Response {
val initialRequest = chain.request()
val strategy = CacheUtil.getCacheStrategy(initialRequest)
val newRequest = initialRequest.rmCacheHeader()
if (strategy == null) return chain.proceed(newRequest)// 策略为空,直接返回网络结果
// ONLY_NETWORK 直接请求网络
if (strategy.cacheMode == CacheMode.ONLY_NETWORK) return chain.proceed(newRequest)
// ONLY_CACHE 只读取缓存
if (strategy.cacheMode == CacheMode.ONLY_CACHE) {
// 只读缓存模式,缓存为空,返回错误响应
return (if (CacheManager.useExpiredData) mCache.getCache(strategy.cacheKey, newRequest)
else redCache(strategy, newRequest)) ?: Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(HttpURLConnection.HTTP_GATEWAY_TIMEOUT)
.message("no cached data")
.body(EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
}
//先取缓存再取网络
if (strategy.cacheMode == CacheMode.READ_CACHE_NETWORK_PUT) {
val cacheResponse = redCache(strategy, newRequest)
if (cacheResponse != null) return cacheResponse
}
try {
// 开始请求网络
val response = chain.proceed(newRequest)
// 成功后写入缓存
if (response.isSuccessful) {
return cacheWritingResponse(mCache.putCache(strategy.cacheKey, response), response)
}
if (strategy.cacheMode == CacheMode.NETWORK_PUT_READ_CACHE) {
return redCache(strategy, newRequest) ?: response
}
return response
} catch (e: Throwable) {
//请求失败尝试读取缓存,缓存没有或者失效,抛异常
if (strategy.cacheMode == CacheMode.NETWORK_PUT_READ_CACHE) {
return redCache(strategy, newRequest) ?: throw e
}
throw e
}
}
设置缓存
这里不得不佩服 Retrofit 在解耦方面做的是真的强啊。我何时能有那样的思路跟想法呢。眼里只有崇拜~~~
言归正传 Retrofit 的请求头是在 Service里边添加的,所以添加缓存策略,直接写在Service里。Retrofit 两种添加请求头的方式 @Headers
是方法注解,@Header
是参数注解。再结合Kotlin 语法可以指定默认参数,如有不同缓存模式就可以在请求的时候,去动态使用不同缓存模式。
/**
* 使用 Header 参数注解
*/
@FormUrlEncoded
@POST("user/login")
suspend fun login(
@Field("username") username: String,
@Field("password") password: String,
@Header(CacheStrategy.CACHE_MODE) cacheMode: String = CacheMode.READ_CACHE_NETWORK_PUT,
@Header(CacheStrategy.CACHE_TIME) cacheTime: String = "10"// 过期时间,10秒 不过期
): BaseResponse<Any>
/**
* 使用 Headers 方法注解
*/
@Headers(
"${CacheStrategy.CACHE_TIME}:-1", // 过期时间,-1 不过期
"${CacheStrategy.CACHE_MODE}:${CacheMode.READ_CACHE_NETWORK_PUT}"
)
@GET("article/list/{page}/json")
suspend fun getPage(@Path("page") page: Any): BaseResponse<Page<ArticleBean>>
缓存的读写
读写操作还是用的OkHttp
的 DiskLruCache
类。Okhttp
4.0.0 版本以后 就用 Kotlin
重构了。DiskLruCache
的构造函数被 internal
修饰了。重构后的前几个版本还提供了 静态方法来创建。后边版本直接静态方法都移除了,这是要搞事情啊,不准备给我们用的样子。不过如果用Java
写的话就可以直接创建,Java
会忽视 internal
关键字直接过编译期。但是 Kotlin
就不行了,会报错。又不想用Java
写。还是直接用反射创建吧,没有反射干不了的事情。
internal fun getDiskLruCache(
fileSystem: FileSystem?,
directory: File?,
appVersion: Int,
valueCount: Int,
maxSize: Long
): DiskLruCache {
val cls = DiskLruCache::class.java
return try {
val runnerClass = Class.forName("okhttp3.internal.concurrent.TaskRunner")
val constructor = cls.getConstructor(
FileSystem::class.java,
File::class.java,
Int::class.java,
Int::class.java,
Long::class.java,
runnerClass
)
constructor.newInstance(
fileSystem,
directory,
appVersion,
valueCount,
maxSize,
TaskRunner.INSTANCE
)
} catch (e: Exception) {
try {
val constructor = cls.getConstructor(
FileSystem::class.java,
File::class.java,
Int::class.java,
Int::class.java,
Long::class.java,
Executor::class.java
)
val executor = ThreadPoolExecutor(
0, 1, 60L, TimeUnit.SECONDS,
LinkedBlockingQueue(), threadFactory("OkHttp DiskLruCache", true)
)
constructor.newInstance(
fileSystem,
directory,
appVersion,
valueCount,
maxSize,
executor
)
} catch (e: Exception) {
throw IllegalArgumentException("Please use okhttp 4.0.0 or later")
}
}
}
刚好4.0.0 之后的几个版本,构造函数要提供一个线程池,4.3.0 后的版本成了 TaskRunner
了。可以都兼容一下。
具体的读写IO操作在CacheManager.kt 这个类中,这个是根据Okhttp 的 Cache
修改而来的。
全局参数
增加了全局 设置缓存模式、缓存时间。优先级还是 Service 中声明出来的高。
CacheManager.setCacheModel(CacheMode.READ_CACHE_NETWORK_PUT)// 设置全局缓存模式
.setCacheTime(15 * 1000) // 设置全局 过期时间 (毫秒)
.useExpiredData(true)// 缓存过期时是否继续使用,仅对 ONLY_CACHE 生效
我这里整理了一套OKhttp源码解析的学习文档,提供给大家进行学习参考,有需要的可以 点击这里直接获取!!!里面记录许多Android 相关学习知识点。
边栏推荐
- DevOps 团队如何抵御 API 攻击?
- 进程间通信---对管道的详细讲解(图文案例讲解)
- Realization of digital tube display based on C51
- [mqtt from introduction to improvement series | 09] Wireshark packet capturing and analyzing mqtt messages
- Navigation -- realize data transmission and data sharing between fragments
- 响应式织梦模板家装装饰类网站
- 12. < tag dynamic programming and subsequence, subarray> lt.72. edit distance
- 【golang学习笔记2.2】 Map、结构体和接口
- 高效使用浏览器的5个小技巧,第1个技巧最实用
- NPM install reports an error: eperm: operation not permitted, rename
猜你喜欢
防止勒索软件攻击数据的十种方法
Detailed explanation of IVX low code platform series -- Overview (II)
Day 15 (VLAN related knowledge)
[email protected] The localization rate reaches 100%"/>
Quanzhi t3/a40i industrial core board, 4-core [email protected] The localization rate reaches 100%
裂开了,一次连接池参数导致的雪崩问题
STM32 DMA receives serial port data
Navigation--实现Fragment之间数据传递和数据共享
【ONE·Data || 链式二叉树】
Responsive dream weaving template hotel room website
TI C6000 TMS320C6678 DSP+ Zynq-7045的PS + PL异构多核案例开发手册(2)
随机推荐
In 2022, the official data of programming language ranking came, which was an eye opener
Cookies and sessions
C语言提高篇(一)
响应式织梦模板户外露营类网站
2022.7.28-----leetcode.1331
C language improvement (I)
什么是作用域和作用域链
【质量】代码质量评价标准
STM32 DMA receives serial port data
ES6 语法扩展
The problem of modifying the coordinate system of point cloud image loaded by ndtmatching function in autoware
"Wei Lai Cup" 2022 Niuke summer multi school training camp 2, sign in question GJK
响应式织梦模板家装建材类网站
Summarize in the middle of the year | talk to yourself, live in the present, and count every step
防止重复点击
年中总结 | 与自己对话,活在当下,每走一步都算数
Responsive Zhimeng template decoration design website
Waiting queue wait_ queue
MotionLayout--在可视化编辑器中实现动画
Rgbd point cloud down sampling