说明
需求是这的:软件启动时网络接口走本地缓存,下拉刷新时做真正的网络请求。
坑
正常情况下用okhttp的缓存功能,但是比较坑的一点是:一旦命中缓存后NetworkInterceptor不再执行GitHub。
// If we don't need the network, we're done.
// 有缓存的时候,直接将缓存返回
if (networkRequest == null) {
// ↓ 这里缓存命中,直接返回
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
//
// .....
//
var networkResponse: Response? = null
try {
// ↓网络拦截器调用位置
networkResponse = chain.proceed(networkRequest)
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
cacheCandidate.body?.closeQuietly()
}
}
实现
追查cacheResponse
的赋值情况,可找到缓存命中的规则:GitHub,其中比较重要的是key
函数GitHub。
再看一下缓存保存的时机:GitHub
// 网络请求返回内容
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
if (cache != null) {
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
// ↓ 放入缓存
val cacheRequest = cache.put(response)
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
// This will log a conditional cache miss only.
listener.cacheMiss(call)
}
}
}
}
其中重点看一下cache.put(response)
这个方法:GitHub,这个方法比较长,这里截取一下片段:
internal fun put(response: Response): CacheRequest? {
val requestMethod = response.request.method
// ...
// 只缓存 GET 的请求方式
if (requestMethod != "GET") {
// Don't cache non-GET responses. We're technically allowed to cache HEAD requests and some
// POST requests, but the complexity of doing so is high and the benefit is low.
return null
}
// ...
// 缓存写入逻辑
val entry = Entry(response)
var editor: DiskLruCache.Editor? = null
try {
// ↓ 这里是根据 response.request.url 生成key名称
editor = cache.edit(key(response.request.url)) ?: return null
entry.writeTo(editor)
return RealCacheRequest(editor)
} catch (_: IOException) {
abortQuietly(editor)
return null
}
}
知道了缓存的命中和保存步骤,那么实现就比较简单:
真正做网络请求的时候让缓存不命中,在网络回调来之后缓存命中更新缓存。
看懂了key的生成规则,那么就可以很容易的控制缓存是否命中,我这里使用的方法:
原始地址:https://www.baidu.com/s?wd=t
缓存地址:https://www.baidu.com/s?wd=t&__is_use_cache__=true # 命中 - 走缓存
网络地址:https://www.baidu.com/s?wd=t&__is_use_cache__=false # 不命中 - 走网络
代码实现:
/**
* Okhttp拦截器:本地缓存拦截器
*
* @author 欢乐马_19e9
* @date 2020-04-08
*/
class InterceptorOkHttpCacheOnline : Interceptor {
companion object {
const val CACHE_QUERY_NAME = "__is_use_cache__"
}
override fun intercept(chain: Interceptor.Chain): Response {
val oldRequest = chain.request()
val newRequest = chain.request().newBuilder()
// 如果当前不支持缓存,直接返回
if (HttpMethod.invalidatesCache(oldRequest.method)) {
return chain.proceed(oldRequest)
}
// 强制使用缓存
// 当 CACHE_QUERY_NAME = false 的情况下不会命中缓存
newRequest.cacheControl(CacheControl.FORCE_CACHE)
// 获取返回状态信息
val response: Response = chain.proceed(newRequest.build())
// 如果返回的不成功,将不进行缓存
if (response.code !in 200..399)
return response
// 修改url让缓存信息命中 CACHE_QUERY_NAME=true 的URL地址
// 并将缓存内容自动保存
val cacheUrl = oldRequest.url.newBuilder().removeAllQueryParameters(name = CACHE_QUERY_NAME).addQueryParameter(name = CACHE_QUERY_NAME, value = "true").build()
newRequest.url(cacheUrl)
// 强行增加缓存信息
return response.newBuilder()
.removeHeader(name = "Pragma")
.removeHeader(name = "Cache-Control")
.addHeader(name = "Cache-Control", value = "public, max-age=${CacheControl.FORCE_CACHE.maxStaleSeconds}")
.request(newRequest.build()) // 修改原始请求地址,使其命中保存缓存
.build()
}
}
食用
// 一定要为GET方式
@GET(value = "/s")
fun baiduQuery(
@Query(value = "wd") word: String = "40",
// 缓存变量,true表示走缓存
@Query(value = InterceptorOkHttpCacheOnline.CACHE_QUERY_NAME) isUseCache: Boolean = false
): Single<String>
缺点
请求的时候会多携带一个CACHE_QUERY_NAME
的参数,可能会影响验签的逻辑。