三、OkHttp_缓存

一、OKHttp的缓存逻辑

OKHttp 把重复请求的数据缓存在本地,并设置超时时间,在规定时间内,客户端不再向远程请求数据,而是直接从本地缓存中取数据。

  • 一来提高了响应速度,
  • 二来节省了网络带宽(也就是节省了钱)

二、Http协议的缓存机制

HTTP缓存机制是Web浏览器、代理服务器和Web服务器之间的协议,用于提高Web性能并减少网络带宽的使用。缓存机制能够在再次请求相同资源时,避免网络数据的重复传输,从而提高网络效率和响应速度。

1.1.HTTP缓存机制主要分为两类:

客户端缓存和服务器缓存。

  • 客户端缓存是指在Web浏览器中缓存常用资源,如图片、JavaScript文件和样式表等。
  • 服务器缓存是指在代理服务器或Web服务器上缓存动态生成的页面或页面元素。
1.2.HTTP缓存机制中有两个重要的概念:

缓存有效期和缓存验证。

  • 缓存有效期是指缓存的资源能够被使用的时间长度。如果在有效期内再次请求相同的资源,缓存就会直接使用缓存中的数据,而不必再向服务器请求数据。
  • 缓存验证是指在缓存过期之后,如果缓存仍然需要使用,就必须向服务器发送一个请求,询问服务器是否有更新的数据。
1.3.HTTP缓存机制可以通过HTTP头部信息来控制缓存的行为。

常用的HTTP头部信息有:

  • Cache-Control:用于控制缓存的行为,包括缓存有效期、缓存验证和缓存控制等。
  • Expires:缓存资源的过期时间,是一个固定的时间点,一旦超过这个时间点就认为缓存已经过期。
  • Last-Modified:资源的最后修改时间,用于判断资源是否有更新。
  • ETag:一个标识符,用于判断资源是否有更新。
1.4.Http缓存相关控制的请求头
1.4.1.Cache-Control
  • no-cache:不做缓存
  • max-age:这个参数告诉浏览器将页面缓存多长时间,超过这个时间后才再次向服务器发起请求检查页面是否有更新。对于静态的页面,比如图片、CSS、Javascript,一般都不大变更,因此通常我们将存储这些内容的时间设置为较长的时间,这样浏览器会不会向浏览器反复发起请求,也不会去检查是否更新了。
  • no-store:数据不在硬盘中临时保存,这对需要保密的内容比较重要。
  • max-stale:指示客户机可以接收超出超时期间的响应消息。如果指定- max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
1.4.2.Expires
  • Expires指定响应过期的日期和时间,其格式为GMT格式的字符串,例如:
Expires: Wed, 21 Oct 2023 07:28:00 GMT

该响应告知客户端,在过期日期之后,该响应将被视为过时,需要重新请求。过期日期应为服务器当前时间和数据生命周期之和。

Expires头的主要缺点在于,它需要客户端和服务器之间的时间同步。如果两者的时间不同步,就可能导致缓存行为不正确。

通常,更推荐使用Cache-Control头来控制响应的缓存行为,因为它提供了更多的灵活性和精度。在Cache-Control和Expires同时存在的情况下,Cache-Control优先级更高。

1.4.3.Last-Modified/If-Modified-Since

HTTP响应头Last-Modified指示资源的最后修改时间。需要配合Cache-Control使用:

  • Last-Modified:标示这个响应资源的最后修改时间。web服务器在响应请求时,告诉浏览器资源的最后修改时间。
  • If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示请求时间。
    web服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。
    若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;
    若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的cache。
Last-Modified: Fri, 29 Oct 2021 08:50:00 GMT

这表示资源在GMT时间2021年10月29日08:50:00进行了最后修改。

1.4.4.Etag/If-None-Match

这个也需要配合Cache-Control使用

  • Etag对应请求的资源在服务器中的唯一标识(具体规则由服务器决定),比如一张图片,它在服务器中的标识为ETag: W/”ACXbWXd1n0CGMtAd65PcoA==”。

  • If-None-Match 如果浏览器在Cache-Control:max-age=60设置的时间超时后,发现消息头中还设置了Etag值。然后,浏览器会再次向服务器请求数据并添加In-None-Match消息头,它的值就是之前Etag值。服务器通过Etag来定位资源文件,根据它是否更新的情况给浏览器返回200或者是304。

Etag机制比Last-Modified精确度更高,如果两者同时设置的话,Etag优先级更高。

1.4.5.Pragma

Pragma头域用来包含实现特定的指令,最常用的是Pragma:no-cache。
在HTTP/1.1协议中,它的含义和Cache- Control:no-cache相同。

三、Http缓存机制流程图

在这里插入图片描述

四、OKHttp设置缓存

参考官方文档

五、CacheInterceptor源码分析

CacheInterceptor 用于处理缓存逻辑。它的主要功能是根据缓存策略决定是否使用缓存的响应,以及是否将新的响应存储到缓存中。

  • 检查是否有缓存:CacheInterceptor首先会检查是否存在与当前请求匹配的缓存响应。如果存在缓存响应并且未过期,将直接返回缓存响应,而不会发送请求到服务器。

  • 发送网络请求:如果没有可用的缓存响应或缓存响应已过期,CacheInterceptor会继续将请求传递给下一个拦截器,发送网络请求到服务器获取新的响应。

  • 处理服务器返回的响应:当服务器返回响应后,CacheInterceptor会根据缓存策略决定是否将该响应存储到缓存中。如果响应满足缓存条件,CacheInterceptor会将其存储到缓存中,以便后续请求可以使用缓存响应。

// 通过构造方法将 Okhttp设置 cache传递到 CacheInterceptor 中
class CacheInterceptor(internal val cache: Cache?) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {      
    val call = chain.call()
      // 1、获取缓存候选者。如何获取???待分析
    val cacheCandidate = cache?.get(chain.request())

    val now = System.currentTimeMillis()
    // 按道理讲,接下来应该判断 是否命中缓存,从而决定是直接请求网络数据还是判断缓存是否过期,
    // 但 CacheInterceptor 中并没有这么做,而是构建了一个 CacheStrategy,实际上它是将缓存的合法性、
   // 缓存是否过期等判断全部放到 CacheStrategy 的构建过程中来做了。
   // // 2、根据 当前时间、请求request、以及缓存候选者 获取策略
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    // 3、获取网络请求Request 和缓存Response
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse
    
    cache?.trackResponse(strategy)
    val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
    // 4、缓存候选不适用。关闭它。
    if (cacheCandidate != null && cacheResponse == null) {
      cacheCandidate.body?.closeQuietly()
    }

    // 5、如果我们被禁止使用网络,但缓存并不存在时,构造一个 504 错误。
    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)")
          .body(EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build().also {
            listener.satisfactionFailure(call, it)
          }
    }

    // 6、缓存任然未过期,不需要使用网络请求,直接返回缓存。
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build().also {
            listener.cacheHit(call, it)
          }
    }
   // 当程序运行到这里时,有两种可能:
   // 一是直接发起网络请求,获取原始数据(networkRequest != null && cacheResponse == null);
   // 二是需要进行条件验证(networkRequest != null && cacheResponse != null)。
    if (cacheResponse != null) {
      listener.cacheConditionalHit(call, cacheResponse)
    } else if (cache != null) {
      listener.cacheMiss(call)
    }
    
    var networkResponse: Response? = null
    try {
      // 7、至此需要发起真正的网络请求:即 networkRequset !=null 
      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()
      }
    }

    // 8、发起网络请求后,即 networkRequset !=null 而且  cacheResponse != null
    // 如果服务器返回 304 Not Modified,则表示缓存未修改,任然可用,更新缓存的 header;
    // 否则表示缓存过期,服务器会直接返回原始数据
    if (cacheResponse != null) {
        //  8.1、判断网络请求的响应码是否是304?
      if (networkResponse?.code == HTTP_NOT_MODIFIED) {
          // 8.1.1、如果响应码是 304 ,则 可以使用缓存 
        val response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers, networkResponse.headers))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis)
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build()

        networkResponse.body!!.close()

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache!!.trackConditionalCacheHit()
        // 8.1.1.1、更新缓存
        cache.update(cacheResponse, response)
        return response.also {
          listener.cacheHit(call, it)
        }
      } else {
          // 8.1.2、如果响应码不是 304,则关闭缓存
        cacheResponse.body?.closeQuietly()
      }
    }
    // 9、构建网络响应response
    val response = networkResponse!!.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build()

    if (cache != null) {
        // 10、如果响应可以被缓存的话,保存缓存。
        // 而且 网络响码符合规则 
        // 而且 response 、和networkRequest是 可以缓存的
      if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
        // 10.1、保存、设置、put新的缓存
        val cacheRequest = cache.put(response)
        // 10.2 、写入缓存,并返回 response
        return cacheWritingResponse(cacheRequest, response).also {
          if (cacheResponse != null) {
            // This will log a conditional cache miss only.
            listener.cacheMiss(call)
          }
        }
      }
        // 11、判断请求方法是否是遗弃缓存??? 
        // invalidatesCache(method: String): Boolean = (method == "POST" || method == "PATCH" 
        // || method == "PUT" || method == "DELETE" ||    method == "MOVE") 
      if (HttpMethod.invalidatesCache(networkRequest.method)) {
        try {
            // 这些请求方法 则移除缓存
          cache.remove(networkRequest)
        } catch (_: IOException) {
          // The cache cannot be written.
        }
      }
    }
    // 12 、返回response
    return response
  }

通过对CacheInterceptor的流程梳理,有几个问题需要再次深入研究分析一下:

  • 缓存的获取逻辑
  • 缓存策略构建缓存的过程
  • 缓存更新过程
  • 缓存添加过程
  • 缓存写入过程
5.1.缓存的获取逻辑

val cacheCandidate = cache?.get(chain.request())分析:
Cache - get()方法源码:

internal fun get(request: Request): Response? {
    // 分析一
  val key = key(request.url)
  // 分析二  
  // 表示一个缓存快照,即表示一个缓存条目的快照状态。
    // 在OkHttp的缓存系统中,当从缓存中获取数据时,会返回一个Snapshot对象。
    // Snapshot对象提供了对缓存数据的访问和操作方法,
    // 包括读取缓存数据、获取缓存数据的输入流、获取缓存数据的大小等。
  val snapshot: DiskLruCache.Snapshot = try {
    cache[key] ?: return null
  } catch (_: IOException) {
    return null // Give up because the cache cannot be read.
  }
    // 分析三
  val entry: Entry = try {
    Entry(snapshot.getSource(ENTRY_METADATA))
  } catch (_: IOException) {
    snapshot.closeQuietly()
    return null
  }
    // 分析四:
  val response = entry.response(snapshot)
  if (!entry.matches(request, response)) {
    response.body?.closeQuietly()
    return null
  }

  return response
}
分析一、通过请求url获取 存储的key
key = url.toString().encodeUtf8().md5().hex()
分析二、通过key从OkHttp自定义的DiskLruCache获取snapshot:快照

val snapshot: DiskLruCache.Snapshot = try {
cache[key] ?: return null

operator fun get(key: String): Snapshot? {
	//  2.1 初始化日志文件,主要是从journal文件中读取内容 分装到 Entry(对象中)
	//  并将其添加到 lruEntries
    initialize()
	// 2.2 验证key是否符合规范:regex [a-z0-9_-]{1,120}
    validateKey(key)
    // 从lruEntries 对象中读取指定的 entry
    val entry = lruEntries[key] ?: return null
    // 2.4 用于获取缓存条目的快照。
    // 快照是缓存条目的一个副本,它允许我们读取缓存的数据而不影响原始缓存条目。
    val snapshot = entry.snapshot() ?: return null

    redundantOpCount++
    // 2.5 在日志文件中写入,读取的记录
    journalWriter!!.writeUtf8(READ)
        .writeByte(' '.toInt())
        .writeUtf8(key)
        .writeByte('\n'.toInt())
    if (journalRebuildRequired()) {
      cleanupQueue.schedule(cleanupTask)
    }
    return snapshot
  }

分析2.1 initialize

fun initialize() {
    this.assertThreadHoldsLock()
	// 已经初始化 ,则return
    if (initialized) {
      return // Already initialized.
    }
	//2.1.1 备份文件存在就使用
    if (fileSystem.exists(journalFileBackup)) {
      // 如果日志文件也存在,则删除备份文件
      if (fileSystem.exists(journalFile)) {
       // 日志文件存在 删除备份文件
        fileSystem.delete(journalFileBackup)
      } else {
      	//  日志文件不存在,将备份文件命名为日志文件。
        fileSystem.rename(journalFileBackup, journalFile)
      }
    }

    civilizedFileSystem = fileSystem.isCivilized(journalFileBackup)

    // 2.1.2 判断日志文件是否存在
    if (fileSystem.exists(journalFile)) {
      try {
         // 2.1.2.1、读取日志文件, 并将 读取的内容封装到 Entry()对象中
         // 并添加到 存放到 lruEntries中
        readJournal()
        // 2.1.2.2、作为打开缓存的一部分,计算初始大小并收集垃圾。
        // 脏条目被认为是不一致的,将被删除。
        processJournal()
        initialized = true
        return
      } catch (journalIsCorrupt: IOException) {
        Platform.get().log(
            "DiskLruCache $directory is corrupt: ${journalIsCorrupt.message}, removing",
            WARN,
            journalIsCorrupt)
      }
      try {
      // 2.1.3 日志文件不存在,或者发生异常
	 // 缓存已经损坏、尝试删除该目录内容。可以抛出异常,因为存在严重的文件系统问题
        delete()
      } finally {
        closed = false
      }
    }
	// 创建新的日志,如果当前的日志文件不存在 ,则替换
    rebuildJournal()

    initialized = true
  }

分析 2.1.2.1 readJournal

  private fun readJournal() {
    fileSystem.source(journalFile).buffer().use { source ->
        // 读取日志文件的头部信息
        val magic = source.readUtf8LineStrict() // 魔数
        val version = source.readUtf8LineStrict() // 版本号
        val appVersionString = source.readUtf8LineStrict() // 应用程序版本号
        val valueCountString = source.readUtf8LineStrict() // 键值对数量
        val blank = source.readUtf8LineStrict() // 空行

        // 检查日志文件头部的有效性
        if (MAGIC != magic ||
            VERSION_1 != version ||
            appVersion.toString() != appVersionString ||
            valueCount.toString() != valueCountString ||
            blank.isNotEmpty()) {
            throw IOException("日志文件头部异常: [$magic, $version, $valueCountString, $blank]")
        }

        var lineCount = 0
        // 逐行读取日志文件的内容
        while (true) {
            try {
                // 用于解析并处理日志文件中的一行内容。
                // 在DiskLruCache中,日志文件用于记录缓存条目的状态和操作。
                readJournalLine(source.readUtf8LineStrict())
                lineCount++
            } catch (_: EOFException) {
                break // 日志文件结束
            }
        }

        // 计算日志文件中冗余操作的数量
        redundantOpCount = lineCount - lruEntries.size

        // 如果源文件未读取完整,说明最后一行被截断,需要在追加内容之前重新构建日志文件
        if (!source.exhausted()) {
            rebuildJournal()
        } else {
            // 创建一个新的日志文件写入器,用于后续写入操作
            journalWriter = newJournalWriter()
        }
    }
}

分析 2.1.2.2 的方法 processJournal

/**
 * 处理日志文件,清理无效的缓存条目。
 */
private fun processJournal() {
    // 删除临时的日志文件
    fileSystem.delete(journalFileTmp)

    // 遍历缓存条目,清理无效的条目并计算缓存大小
    val iterator = lruEntries.values.iterator()
    while (iterator.hasNext()) {
        val entry = iterator.next()

        // 如果当前缓存条目没有正在进行的编辑操作,则计算其大小
        if (entry.currentEditor == null) {
            for (t in 0 until valueCount) {
                size += entry.lengths[t]
            }
        } else {
            // 如果当前缓存条目有正在进行的编辑操作,则清理相关文件并移除条目
            entry.currentEditor = null
            for (t in 0 until valueCount) {
                fileSystem.delete(entry.cleanFiles[t])
                fileSystem.delete(entry.dirtyFiles[t])
            }
            iterator.remove()
        }
    }
}
分析三、从缓存快照中取出第一个Source,并构建 Cache.Entry

// 读取:url 、requestMethod、varyHeaders、protocol、code、message、sentRequestMillis、receivedResponseMillis 、responseHeaders 、https的相关信息 …
// 就是读取请求信息
val entry: Entry = try {
Entry(snapshot.getSource(ENTRY_METADATA))

 val source = rawSource.buffer()
 url = source.readUtf8LineStrict()
 requestMethod = source.readUtf8LineStrict()
 ...
 
分析四、通过entry的值构建 response

-> val response = entry.response(snapshot)

fun response(snapshot: DiskLruCache.Snapshot): Response {
      val contentType = responseHeaders["Content-Type"]
      val contentLength = responseHeaders["Content-Length"]
      val cacheRequest = Request.Builder()
          .url(url)
          .method(requestMethod, null)
          .headers(varyHeaders)
          .build()
      return Response.Builder()
          .request(cacheRequest)
          .protocol(protocol)
          .code(code)
          .message(message)
          .headers(responseHeaders)
          .body(CacheResponseBody(snapshot, contentType, contentLength))
          .handshake(handshake)
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(receivedResponseMillis)
          .build()
    }
// 获取构建缓存响应body
-> CacheResponseBody(snapshot, contentType, contentLength)
     // 获得缓存快照source中的第二的文件流
     -> val source = snapshot.getSource(ENTRY_BODY)

六、缓存策略的计算过程

val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()

-> CacheStrategy.Factory()
// 1、通过读取缓存中的 heads 设置成员变量:
//    Date、expires、lastModified、etag、ageSeconds
	-> init()
	// 2、使用cacherresponse返回一个策略来满足请求。
	-> compute()
		// 2.1 
		-> val candidate = computeCandidate()
		// 2.1.1、没有缓存响应
		-> if (cacheResponse == null)
			-> return CacheStrategy(request, null)
		// 2.1.2、如果缺少必要的握手,则删除缓存的响应。
		-> if (request.isHttps && cacheResponse.handshake == null)
			-> return CacheStrategy(request, null)
		// 2.1.3、如果不应该存储此响应,则永远不应该将其用作响应源。
		// 只要持久性存储运行良好且规则不变,这种检查就应该是多余的。
		-> if (!isCacheable(cacheResponse, request))
			// 如果响应可以存储以供以后服务另一个请求,则返回true。
			-> isCacheable()
				// 通过缓存的响应码判断是否能缓存
				-> when (response.code)
					-> return true/false
				// 其次通过 请求的 和缓存的 cacheControl noStore 字段判断
				-> return !response.cacheControl.noStore && !request.cacheControl.noStore
			// 不满足受用缓存的条件
			-> return CacheStrategy(request, null)
			// 2.1.4、判断 请求的 和缓存的 cacheControl noStore 字段判断
			-> if (requestCaching.noCache || hasConditions(request))
				-> return CacheStrategy(request, null)
			// 2.1.5、超时判断
			-> if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) 
				-> return CacheStrategy(null, builder.build())
			// 2.1.6、找到要添加到请求的条件。如果满足条件,则不会传输响应体。
			// 即将符合条件的请求头添加到请求中
			-> etag != null -> {conditionName = "If-None-Match"  conditionValue = etag }
			-> val conditionalRequest = request.newBuilder().headers(conditionalRequestHeaders.build()).build()
			-> return CacheStrategy(conditionalRequest, cacheResponse)

		// 2.2、我们被禁止使用网络,缓存不足。
		-> if (candidate.networkRequest != null && request.cacheControl.onlyIfCached)
		// 2.3、返回计算出来的CacheStrategy
		-> return candidate

七、缓存更新过程

cache.update(cacheResponse, response)

internal fun update(cached: Response, network: Response) {
	// 分析一
    val entry = Entry(network)
    // 分析二
    val snapshot = (cached.body as CacheResponseBody).snapshot
    var editor: DiskLruCache.Editor? = null
    try {
    // 分析三
      editor = snapshot.edit() ?: return // edit() returns null if snapshot is not current.
      entry.writeTo(editor)
      editor.commit()
    } catch (_: IOException) {
      abortQuietly(editor)
    }
  }
分析1、通过网络请求结果 构建, 初始化Entry 成员变量 url、headers、Method、code、message…

-> val entry = Cache.Entry(network)

分析2、获得 缓存body的快照

-> val snapshot = (cached.body as CacheResponseBody).snapshot

分析3、返回此快照条目的编辑器,如果自此快照创建以来条目发生了更改或正在进行另一项编辑,则返回null。
-> editor = snapshot.edit()
	// 3.1  key:string加密值  ,sequenceNumber:最近提交到此条目的编辑的序列号。
	-> this@DiskLruCache.edit(key, sequenceNumber)
		//  已分析过。
		-> initialize()
		// 3.1.1、获取key对应的文件 .0 .1结尾
		-> var entry: Entry? = lruEntries[key]
		// 3.1.2、快照过期
		-> if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER &&
        (entry == null || entry.sequenceNumber != expectedSequenceNumber))
			-> return null
		// 3.1.3、另一项编辑正在进行中。
		-> if (entry?.currentEditor != null) 
			-> return null
		// 3.1.4、我们无法写入该文件,因为阅读器仍在读取它。
		-> if (entry != null && entry.lockingSourceCount != 0)
			->  return null
		// 3.1.5、如果 日志重建失败,日志写入器将不再活动,这意味着我们将不再活动 能够记录编辑,导致文件泄漏。在这           
		// 两种情况下,我们都希望重试清理 这样我们就能摆脱这种状态了!
		-> if (mostRecentTrimFailed || mostRecentRebuildFailed) 
			-> cleanupQueue.schedule(cleanupTask)
			-> return null
		// 3.1.6、在创建文件之前刷新日志,以防止文件泄漏。
		-> journalWriter.writeUtf8(DIRTY)
		-> journalWriter.flush()
		// 3.1.7、创建缓存文件entry
		-> if (entry == null)
			//  创建包含 缓存文件信息 的 DiskLruCache.Entry
			-> entry = Entry(key)
			-> lruEntries[key] = entry
		// 3.1.8、构建editor
		-> val editor = Editor(entry)
		-> entry.currentEditor = editor
		-> return editor
4、将请求头的信息 METADATA 写入文件 .0文件

-> entry.writeTo(editor)

5、提交此编辑,以便读者可以看到。这将释放编辑锁,以便对同一密钥启动另一个编辑。

-> editor.commit()

八、缓存put存放的过程

 val cacheRequest = cache.put(response)
// 1、通过Http请求方式,验证是否支持缓存
// post、patch、put、move
-> if (HttpMethod.invalidatesCache(response.request.method))
	-> remove(response.request) // 下面分析
// 2、不要缓存非get响应。
// 技术上我们允许缓存HEAD请求和一些POST请求,但是这样做的复杂性很高,好处很低。
-> if (requestMethod != "GET")
	-> return null
// 3、如果Vary标头包含星号,则返回true。这样的响应不能被缓存。
-> if (response.hasVaryAll())
	->  return null
// 4、构造Cache.Entry
-> val entry = Entry(response)
-> editor = cache.edit(key(response.request.url)) ?: return null
	-> DiskLruCache.edit()
		// 初始化 缓存的 Journal 文件,并创建 journalWriter 。一分析过
		-> initialize()
		-> var entry: Entry? = lruEntries[key]
		// 在创建文件之前刷新日志,以防止文件泄漏。记录操作日志
		-> val journalWriter = this.journalWriter!!
		-> journalWriter.writeUtf8(DIRTY)... 
		// 创建DiskLruCache.Entry
		-> if (entry == null)
			-> entry = DiskLruCache.Entry(key)
				-> init()
					// valueCount 为2 ,固定值。即创建以kay.0  key.1的两个缓存文件
					-> val fileBuilder = StringBuilder(key).append('.')
						-> for (i in 0 until valueCount)
								-> fileBuilder.append(i)
								->  cleanFiles += File(directory, fileBuilder.toString())
			// 存放 DiskLruCache.Entry
			-> lruEntries[key] = entry
			// 创建Editor
			-> val editor = Editor(entry)
			// 将 DiskLruCache.Entry 和 editor 绑定
			-> entry.currentEditor = editor
// 5、将 写入请求Header等信息
-> entry.writeTo(editor)
	-> editor.newSink(ENTRY_METADATA)
		// 获取 以key.0结尾的缓存文件
		-> val dirtyFile = entry.dirtyFiles[index]
		-> sink = fileSystem.sink(dirtyFile)
	// 写入头信息
	-> sink.writeUtf8(url).writeByte('\n'.toInt())
-> 
// 6、返回 保存的结果信息
-> return RealCacheRequest(editor)
	// 获取 以key.1结尾的缓存文件
	-> val cacheOut: Sink = editor.newSink(ENTRY_BODY)
	// 关联Body 流
	-> RealCacheRequest.body = ForwardingSink(cacheOut)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值