前几天做了一个需求是关于打点的缓存以及上报,看起来跟okhttp的缓存使用差不多,我觉得这个需要详细看看OkHttp是怎么做的。
首先看一下构造函数,需要传入以下几个参数。
//directory - 可写目录。
//valueCount - 每个缓存条目的值数量。必须为正数。
//maxSize - 此缓存用于存储的最大字节数。
//fileSystem -文件系统
constructor(
fileSystem: FileSystem, directory: Path, maxSize: Long,) : this(
directory,
maxSize,
fileSystem,
TaskRunner.INSTANCE,
)
什么时候存入缓存?
如果知道缓存是请求的响应体的缓存,那么就知道肯定是在返回响应时,进行缓存,调用put()方法的地方只有一个。
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
if (cache != null) {
val cacheNetworkRequest = networkRequest.requestForCache()
if (response.promisesBody() && CacheStrategy.isCacheable(response, cacheNetworkRequest)) {
// Offer this request to the cache.
val cacheRequest = cache.put(response.newBuilder().request(cacheNetworkRequest).build())
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
// This will log a conditional cache miss only.
listener.cacheMiss(call)
}
}
}
}
}
怎么存入缓存?
那是怎么缓存的呢?搜索Cache类里,放入缓存的主要通过put()方法,具体如下:
internal fun put(response: Response): CacheRequest? {
val requestMethod = response.request.method
//判断网络请求方式(get/post/put/move等)
if (HttpMethod.invalidatesCache(response.request.method)) {
try {
remove(response.request)
} catch (_: IOException) {
// The cache cannot be written.
}
return null
}
//缓存非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
}
//以*开头的请求头无法缓存
if (response.hasVaryAll()) {
return null
}
//生成缓存实体
val entry = Entry(response)
var editor: DiskLruCache.Editor? = null
try {
//以request.url为缓存名字,写入到缓存中
editor = cache.edit(key(response.request.url)) ?: return null
entry.writeTo(editor)
return RealCacheRequest(editor)
} catch (_: IOException) {
abortQuietly(editor)
return null
}
}
什么时机写入呢?
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
if (cache != null) { //首先,检查是否存在缓存对象。如果存在,继续执行缓存逻辑。
val cacheNetworkRequest = networkRequest.requestForCache()
//判断HTTP响应是否包含主体 && 检查能否缓存(响应成功码)
if (response.promisesBody() && CacheStrategy.isCacheable(response, cacheNetworkRequest)) {
// Offer this request to the cache.
val cacheRequest = cache.put(response.newBuilder().request(cacheNetworkRequest).build())
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
// This will log a conditional cache miss only.
listener.cacheMiss(call)
}
}
}
//如果网络请求的方法是无效的,它会尝试从缓存中移除这个请求。
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
cache.remove(networkRequest)
} catch (_: IOException) {
// The cache cannot be written.
}
}
}
}
可以看到上面的代码中,最重要的除了校验方法,就是缓存方法,稍微看下缓存方法里干了那些事情。
@Throws(IOException::class)
private fun cacheWritingResponse(
cacheRequest: CacheRequest?,
response: Response,
): Response {
// Some apps return a null body; for compatibility we treat that like a null cache request.
if (cacheRequest == null) return response
val cacheBodyUnbuffered = cacheRequest.body()
//获取response的源(body)并将其缓冲化。
val source = response.body.source()
val cacheBody = cacheBodyUnbuffered.buffer()
//创建一个cacheWritingSource对象,它是一个实现了Source接口的匿名对象,用于从response中读取数据并写入cacheRequest
val cacheWritingSource =
object : Source {
private var cacheRequestClosed = false
@Throws(IOException::class)
override fun read(
sink: Buffer,
byteCount: Long,
): Long {
val bytesRead: Long
try {
bytesRead = source.read(sink, byteCount)
} catch (e: IOException) {
if (!cacheRequestClosed) {
cacheRequestClosed = true
cacheRequest.abort() // Failed to write a complete cache response.
}
throw e
}
if (bytesRead == -1L) {
if (!cacheRequestClosed) {
cacheRequestClosed = true
cacheBody.close() // The cache response is complete!
}
return -1
}
sink.copyTo(cacheBody.buffer, sink.size - bytesRead, bytesRead)
cacheBody.emitCompleteSegments()
return bytesRead
}
override fun timeout(): Timeout = source.timeout()
@Throws(IOException::class)
override fun close() {
//close方法在关闭source之前,如果cacheRequest未关闭,它会尝试丢弃并中止cacheRequest
if (!cacheRequestClosed &&
!discard(ExchangeCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)
) {
cacheRequestClosed = true
cacheRequest.abort()
}
source.close()
}
}
val contentType = response.header("Content-Type")
val contentLength = response.body.contentLength()
//使用cacheWritingSource和新的内容类型和内容长度构建一个新的Response对象
return response.newBuilder()
.body(RealResponseBody(contentType, contentLength, cacheWritingSource.buffer()))
.build()
}
什么时候读缓存?
通过源码我们可以看到调用缓存的地方在interceptor,通过intercep接口,调用到具体实现类 CacheInterceptor.
@Throws(IOException::class)
override fun proceed(request: Request): Response {
check(index < interceptors.size)
"拦截器索引超出拦截器列表范围"
calls++
if (exchange != null) {
check(exchange.finder.routePlanner.sameHostAndPort(request.url)) {
"网络拦截器必须保留相同的host和port"
}
check(calls == 1) {
"网络拦截器必须恰好调用一次proceed()"
}
}
// Call the next interceptor in the chain.
val next = copy(index = index + 1, request = request)
val interceptor = interceptors[index]
@Suppress("USELESS_ELVIS")
val response =
interceptor.intercept(next) ?: throw NullPointerException(
"拦截器返回了null",
)
if (exchange != null) {
check(index + 1 >= interceptors.size || next.calls == 1) {
"网络拦截器必须恰好调用一次proceed()"
}
}
return response
}
怎么读读缓存?
在实现类CacheInterceptor中,首先检查有无缓存。
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
//先检查缓存
val cacheCandidate = cache?.get(chain.request().requestForCache())
val now = System.currentTimeMillis()
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
val networkRequest = strategy.networkRequest
val cacheResponse = strategy.cacheResponse
cache?.trackResponse(strategy)
val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
if (cacheCandidate != null && cacheResponse == null) {
// The cache candidate wasn't applicable. Close it.
cacheCandidate.body.closeQuietly()
}
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(HTTP_GATEWAY_TIMEOUT)
.message("Unsatisfiable Request (only-if-cached)")
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build().also {
listener.satisfactionFailure(call, it)
}
}
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(cacheResponse.stripBody())
.build().also {
listener.cacheHit(call, it)
}
}
if (cacheResponse != null) {
listener.cacheConditionalHit(call, cacheResponse)
} else if (cache != null) {
listener.cacheMiss(call)
}
......
}
}
那get()方法是怎么取缓存的呢?
internal fun get(request: Request): Response? {
// 使用key函数生成缓存键 url.toString().encodeUtf8().md5().hex()
val key = key(request.url)
// 尝试从缓存中获取与键关联的Snapshot
val snapshot: DiskLruCache.Snapshot =
try {
cache[key] ?: return null
} catch (_: IOException) {
return null // Give up because the cache cannot be read.
}
// 使用Snapshot创建Entry对象
val entry: Entry =
try {
Entry(snapshot.getSource(ENTRY_METADATA))
} catch (_: IOException) {
snapshot.closeQuietly()
return null
}
// 使用Entry创建Response对象
val response = entry.response(snapshot)
// 检查Entry是否与请求和响应匹配
if (!entry.matches(request, response)) {
response.body.closeQuietly()
return null
}
return response
}
总结
最后用一张图来总结一下,整个okhttp的缓存逻辑。