以 Okhttp3源码 为例 ------ 图解 缓存机制 的原理和实现(下)

之前写的一篇是介绍缓存机制的流程和原理,并讲解了HTTP协议中缓存相关的字段,彻底了解了缓存机制原理后实践了Okhttp3框架的缓存实现,即第一篇的内容已经打下了基础,此篇就从源码的角度来解析Okhttp3框架的缓存机制的实现。

(未看过第一篇,建议先了解缓存机制原理及HTTP协议中的重要字段,链接如下:)
http://blog.csdn.net/itermeng/article/details/52297077

在分析源码之前,提醒大家注意一点,我在之前的博文中也有分析过源码。刚开始去分析某个框架源码时,一定不要过于详细纠结每一个方法中具体实现、操作等等,先把重点放在整体流程的梳理,理解透彻之后当你真需要去了解每个方法细节时,再去详细分析。


一. OKhttp3 缓存机制整体流程

在上篇博客中已列出一个例子来实践Okhttp3框架的缓存机制,这是一个举出的例子,使用Okhttp3进行网络请求,并设置缓存属性,代码如下:

public class CacheHttp {
    public static void main(String args[]) throws IOException {
        int maxCacheSize = 10 * 1024 * 1024;

        Cache cache = new Cache(new File("/Users/nate/source"), maxCacheSize);
        OkHttpClient client = new OkHttpClient.Builder().cache(cache).build();

        Request request = new Request.Builder().url("http://www.qq.com/").
                cacheControl(new CacheControl.Builder().maxStale(365, TimeUnit.DAYS).build()).
                build();

        Response response = client.newCall(request).execute();


        String body1 = response.body().string();
        System.out.println("network response " + response.networkResponse());
        System.out.println("cache response " + response.cacheResponse());

        System.out.println("**************************");

        Response response1 = client.newCall(request).execute();

        String body2 = response1.body().string();
        System.out.println("network response " + response1.networkResponse());
        System.out.println("cache response " + response1.cacheResponse());
    }
}

以上代码已经使用了OKhttp3框架中的缓存机制,接下来从源码角度了解其实现。首先查看请求最终调用方法 —— client.newCall(request).execute() ,其实execute()Call接口中的方法,查看其实现类:RealCall

1. 入口 —— RealCall 类

(1)execute() 方法

【RealCall 类】

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

首先看try方法块,通过getResponseWithInterceptorChain() 获取一个Response响应对象,此方法应该是处理一些请求相关的信息,来具体查看实现:


(2)getResponseWithInterceptorChain()方法

  private Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //☆☆☆☆☆缓存拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!retryAndFollowUpInterceptor.isForWebSocket()) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(
        retryAndFollowUpInterceptor.isForWebSocket()));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }
}

从此方法可以看出Okhttp3 采用了一种拦截机制,在添加拦截器时发现一个类为 CacheInterceptor ,顾名思义,猜测与缓存相关的处理在此拦截器中进行。注意在创建此类时需要往构造函数中传参 —— client.interceptors() ,实际上它是Okhttp3 内部的一个缓存Cache,参看缓存拦截器CacheInterceptor其具体实现:



2. 核心 —— 缓存拦截器CacheInterceptor类的 intercept方法

此类主要缓存逻辑处理是在intercept 方法中进行,以下将方法分成两部分来进行讲解。

(1)intercept 方法上半部分

【CacheInterceptor 类】

@Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }

    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_BODY)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    ......
    //下一节讲解
  }

注意此方法最终返回结果是一个Response响应变量,首先来了解此方法中至关重要的局部变量:

  • Request networkRequest
    网络请求,若此请求不需要使用到网络(即直接从缓存获取),则为null。
  • Response cacheResponse
    缓存响应,若此次请求不使用缓存(即缓存失效的情况下需请求网络资源),则为null。

了解以上两个变量后,得知它是在创建CacheStrategy类后获取的其成员变量。需注意此类是缓存机制的重点,代表为Okhttp3框架中的缓存拦截策略,直接决定此方法的返回值Response响应。它的主要逻辑就是判断当前本地缓存是否有效,若缓存依然有效,则填充cacheResponse缓存响应对象;若无效,则填充networkRequest网络请求对象,而intercept 方法会根据这两个值做下一步的判断。(CacheStrategy类源码具体实现后续会介绍,这里先将流程走通,读者此时有个大致认识即可。)

根据判断这两个对象是否为null值,来决定返回结果,流程图如下:

这里写图片描述

可以发现,CacheInterceptor类的 intercept方法主要判断流程是根据 网络请求对象networkRequest 和 缓存响应对象cacheResponse决定的,它们存在的可能情况有以下三种:

  • 网络请求对象、缓存响应对象皆为null
  • 网络请求对象为null,缓存响应对象不为null
  • 网络请求对象不为null,(缓存对象为null或不为null)

    intercept方法的上半部分已经判断了以上两种情况,接下来最后一种情况在下半部分逻辑。注意最后一种情况,当网络请求对象不为null时,表明CacheStrategy缓存策略类判断后需要进行网络请求,也意味着本地缓存对象已失效,所以此时的缓存对象为null与否并非决定性关键了。

再来具体查看下半部分逻辑:


(2)intercept 方法下半部分

【CacheInterceptor 类】

@Override public Response intercept(Chain chain) throws IOException {
    ......
    //接上一节
    Response networkResponse = 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) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (validate(cacheResponse, networkResponse)) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .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();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (HttpHeaders.hasBody(response)) {
      CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
      response = cacheWritingResponse(cacheRequest, response);
    }

    return response;
   }

这下半部分的前提判断条件是:网络请求对象不为null,缓存对象为null或不为null。首先了解此方法中至关重要的局部变量:

  • Response networkResponse
    请求网络后的响应结果对象
  • Response response
    最终返回对象

它的主要逻辑是执行新的网络请求,最终返回response响应对象,至于第一个networkResponse响应对象则是用于执行网络请求获取资源数据。查看其源码,以流程图来解释逻辑:

这里写图片描述

这就是缓存拦截器CacheInterceptor类的 intercept方法的主要逻辑,此方法是Okhttp3框架处理缓存的关键!



3. CacheInterceptor类的 intercept方法的总流程图:

这里写图片描述





二. 细节解析 —— CacheStrategy 缓存策略类

以上讲解了处理缓存的整体流程,在了解大体流程后,接下来详细解析流程中的几个重要部分,正好迎合了一开头提给大家的建议:分析源码时,先了解整体过程,再从细节入手分析,接下来来讲解缓存策略器是如何让做相应的判断:

1. 原理图解

这里写图片描述

此图在上篇文章中已经讲解过,(详细讲解可砍上篇文章)CacheStrategy 缓存策略类中的逻辑原理就是上图流程所示,这里简单叙述流程

当客户端请求资源时,首先判断本地是否有缓存,在有缓存的情况下判断它是否过期,若已经过期再判断Etag标识值是否存在(此值不是必须存在):

  • 如果存在便会向服务器发送请求,在请求时会携带参数,参数If-None-Match会将Etag标记上一起发送给服务器。服务器再决策Etag是否过期,根据返回的响应码来决定从缓存读取还是请求响应。

  • 如果不存在,会再次判断 Last-Modified字段是否存在,如果存在的话,如同Etag一样会向服务器发送请求,只是携带参数是会用If-Modified-Since去标识Last-Modified字段,一起发送给服务。在服务器决策时,会将Last-Modified与服务器修改文件的时间进行比较,若无变化则直接从缓存中读取,否则请求响应,接收新的数据。



2. CacheStrategy 缓存策略类的创建 —– Factory()

CacheStrategy 缓存策略类 遵循以上图解流程的这样一套规则,接下来从真正源码的角度来解析其实现。此类的调用是在CacheInterceptor类的 intercept方法里:

【CacheInterceptor 类】
@Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
    ......
    }

CacheStrategy 策略缓存类的狗仔是在其静态方法Factory 中,这里很明显的运用了工厂模式,传递了三个参数:

- now :是当前请求资源时间,将用于校验缓存是否过期。
- chain.request() : 一个Request对象,当前请求头的参数。
- cacheCandidate : 一个Response对象,如果当前的缓存目录(这里的缓存目录是cache,类型为InternalCache)不等于null,则从缓存目录中取出对应请求的Response数据。

继续深入,了解其CacheStrategy 类具体创建实现:

【CacheStrategy 类 】

public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;
      this.request = request;
      this.cacheResponse = cacheResponse;

      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            ageSeconds = HttpHeaders.parseSeconds(value, -1);
          }
        }
      }
    }

这里首要的就是判断从缓存目录中读取的数据cacheResponse 是否为空,只有在不为空的情况下才可以取出响应头中的数据,在if语句块中采用循环查找对应的几个标识符进行赋值。这几个标识符分别为”Date”、”Expires”、Last-Modified”、”ETag”、”Age”,注意并非每个标识符都存在于缓存数据中,例如上篇博客中的测试缓存腾讯网,只包含”Date”、”Expires”标识符。

循环中的赋值主要是为了填充CacheStrategy 类的两个重要局部变量:cacheResponse缓存响应对象和networkRequest网络请求对象。它们决定着Okhttp3框架缓存机制的核心方法 —— CacheInterceptor类的 intercept方法的核心逻辑。



3. CacheStrategy 类中局部变量的获取 —— get()

【CacheInterceptor 类】

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

CacheStrategy 类的创建方法分析完后还没有结束,以上只是填充了两个局部变量,但是别忘了在CacheInterceptor类的 intercept方法中获取CacheStrategy 类最终调用是 get 方法,来查看其具体实现:

【CacheStrategy 类】

    public CacheStrategy get() {
    //getCandidate()才是真正逻辑判断实现 ☆☆☆☆☆
      CacheStrategy candidate = getCandidate();

      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
        // We're forbidden from using the network and the cache is insufficient.
        return new CacheStrategy(null, null);
      }

      return candidate;
    }

主要的就是调用getCandidate() 方法获取缓存策略类并返回,所以此方法才是真正处理缓存是否失效的核心逻辑方法,查看其实现(小河里也将该方法分成上下两部分来分别解析):

(1)getCandidate() 的上半部分代码解析

【CacheStrategy 类】

 private CacheStrategy getCandidate() {
      // No cached response.
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }

      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }

      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }
//----------上部分为返回缓存响应为null的情况-------------

//----------下部分为判断缓存时间是否失效逻辑-------------
      ......
    }

getCandidate() 方法的上半部分主要是返回无缓存响应Response对象的缓存策略类CacheStrategy ,即必定请求网络资源的情况,这里通过四种不同情况去判断,以下流程图分析其过程:

这里写图片描述


(2)getCandidate() 的下半部分代码解析 ☆☆☆☆☆

private CacheStrategy getCandidate() {
      ......
//----------上部分为返回缓存响应为null的情况-------------

//----------下部分为判断缓存时间是否失效逻辑-------------

      long ageMillis = cacheResponseAge();
      long freshMillis = computeFreshnessLifetime();

      if (requestCaching.maxAgeSeconds() != -1) {
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }

      long minFreshMillis = 0;
      if (requestCaching.minFreshSeconds() != -1) {
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }

      long maxStaleMillis = 0;
      CacheControl responseCaching = cacheResponse.cacheControl();
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }

       //缓存未过期的情况
      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
        if (ageMillis + minFreshMillis >= freshMillis) {
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
        }
        return new CacheStrategy(null, builder.build());
      }


      //缓存过期的情况
      String conditionName;
      String conditionValue;
      if (etag != null) {
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if (lastModified != null) {
        conditionName = "If-Modified-Since";
        conditionValue = lastModifiedString;
      } else if (servedDate != null) {
        conditionName = "If-Modified-Since";
        conditionValue = servedDateString;
      } else {
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }

      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }

此方法的下半部分主要在判断缓存响应Response,这部分的处理逻辑与第二大点开头讲解的图片缓存机制原理相对应,可谓是缓存机制的精髓!同样将其分成两种不同情况进行处理(我注释的两个if语句块):

缓存未过期的情况

return new CacheStrategy(null, builder.build());

查看其返回结果可知,若缓存未过期,在创建CacheStrategy类时,网络请求对象为null,将其缓存数据传入,即代表此请求无需请求网络,重复利用缓存数据即可。

缓存已过期的情况(逻辑步骤)

  • (a)首先判断Etag字段是否为null,若不为null,则携带参数If-None-Match此参数需与服务器校验本地过期的缓存是否需要更新,而服务器通过比较客户端与自身的这些字段标识来判断是否需要通知客户端进行缓存更新。

  • (b)Etag字段为null,则继而判断lastModified字段是否为null,若不为null,则携带参数If-Modified-Since,理由同上。

  • (c)lastModified字段为null,则继而判断servedDate字段是否为null,若不为null,则携带参数If-Modified-Since,这里缓存机制提供了三种字段去进行判断,因为并不是每个响应资源的字段中包含这些数据。

  • (d) 若缓存中以上字段皆为null,则意味着此缓存资源过期且无使用价值,还是需要请求网络资源获取最新资源,则创建CacheStrategy类时,缓存响应对象为null。

return new CacheStrategy(request, null);
  • (e)若以上三种字段有其一不为null,则将网络请求头重新进行封装,即在现有的字段上增加了新的携带参数,最终创建CacheStrategy类,传入新封装好的网络请求参数request、现有的缓存响应response。


4. 小结

以上就是缓存策略类的详细讲解,其中缓存机制的精髓就在其类的逻辑判断方法中 —— getCandidate(),重点放在缓存机制的原理图解和此方法的解析!





三. 细节补充 —— Cache 和 DiskLruCache

以上内容通过详细分析缓存拦截器CacheInterceptor类的 intercept方法和 CacheStrategy 缓存策略类的分析,已经对Okhttp3框架的缓存机制的整体流程、重点类了解渐深,这里再补充一点细节,有关CacheDiskLruCache类的介绍。

1. Cache类初识

首先思绪还是回到最初拦截器的创建代码,如下:

【RealCall 类】
interceptors.add(new CacheInterceptor(client.internalCache()));

这里传入CacheInterceptor类构造方法的参数是:

【OkHttpClient 类】
  InternalCache internalCache() {
    return cache != null ? cache.internalCache : internalCache;
  }

主要逻辑是判断cache(此变量代表当前程序可以指定的cache)是否为空,不为空则返回cache的内部缓存,为空则返回OkHttpClient 的内部缓存。而这个变量cache是Cache类,查看起部分源码:


2. Cache类介绍

【Cache 类】
public final class Cache implements Closeable, Flushable {
  private static final int VERSION = 201105;
  private static final int ENTRY_METADATA = 0;
  private static final int ENTRY_BODY = 1;
  private static final int ENTRY_COUNT = 2;

  final InternalCache internalCache = new InternalCache() {
    @Override public Response get(Request request) throws IOException {
      return Cache.this.get(request);
    }

    @Override public CacheRequest put(Response response) throws IOException {
      return Cache.this.put(response);
    }

    @Override public void remove(Request request) throws IOException {
      Cache.this.remove(request);
    }

    @Override public void update(Response cached, Response network) {
      Cache.this.update(cached, network);
    }

    ......
      private final DiskLruCache cache;
}   

查看其源码可见cache.internalCacheCache 类的内部类,但是需要注意的是Cache最终调用的是DiskLruCache 类,Cache 类只不过是它的封装,调用Cache 类来对本地缓存数据进行修改、删除等操作。


3. 操作本地缓存

继续来探究操作本地缓存的问题,当我们调用Okhttp3框架api往本地缓存中添加数据时,CacheInterceptor类的maybeCache 方法,源码如下:

  private CacheRequest maybeCache(Response userResponse, Request networkRequest,
      InternalCache responseCache) throws IOException {
    if (responseCache == null) return null;

    // Should we cache this response for this request?
    if (!CacheStrategy.isCacheable(userResponse, networkRequest)) {
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          responseCache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
      return null;
    }

    // Offer this request to the cache.
    return responseCache.put(userResponse);
  }
【InternalCache 接口】
public interface InternalCache {
  Response get(Request request) throws IOException;

  CacheRequest put(Response response) throws IOException;
  ......
  }

这里操作本地缓存数据最终调用的是responseCache对象的put 方法,查看其方法可知是InternalCache接口中的一个方法,而它具体实现对象便是Cache类,查看Cache类的put 方法:

【Cache 类】
private CacheRequest put(Response response) {
    String requestMethod = response.request().method();
    if (HttpMethod.invalidatesCache(response.request().method())) {
      try {
        remove(response.request());
      } catch (IOException ignored) {
      }
      return null;
    }
    if (!requestMethod.equals("GET")) {
      return null;
    }

    if (HttpHeaders.hasVaryAll(response)) {
      return null;
    }
    Entry entry = new Entry(response);
    DiskLruCache.Editor editor = null;
    try {
      editor = cache.edit(urlToKey(response.request()));
      if (editor == null) {
        return null;
      }
      entry.writeTo(editor);
      return new CacheRequestImpl(editor);
    } catch (IOException e) {
      abortQuietly(editor);
      return null;
    }
  }

真相大白!Cache类的put 方法最终调用的还是DiskLruCache类,即它才是操作本地缓存的底层对象,此方法就是往本地文件中写入缓存数据,首先if判断当前的请求方式是否支持,如果支持的话先移除对应请求的数据,避免重复;然后通过DiskLruCache类获取可书写Editor 对象,然后会根据当前请求的URL做一个MD5的加密,生成的本地缓存名字就是一串字符,如下图所示,获取此文件后,调用Entry的writeTo 方法写入数据即可结束。此方法源码如下:

【Entry 类】
public void writeTo(DiskLruCache.Editor editor) throws IOException {
      BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));

      sink.writeUtf8(url)
          .writeByte('\n');
      sink.writeUtf8(requestMethod)
          .writeByte('\n');
      sink.writeDecimalLong(varyHeaders.size())
          .writeByte('\n');
      for (int i = 0, size = varyHeaders.size(); i < size; i++) {
        sink.writeUtf8(varyHeaders.name(i))
            .writeUtf8(": ")
            .writeUtf8(varyHeaders.value(i))
            .writeByte('\n');
      }

      sink.writeUtf8(new StatusLine(protocol, code, message).toString())
          .writeByte('\n');
      sink.writeDecimalLong(responseHeaders.size() + 2)
          .writeByte('\n');
      for (int i = 0, size = respon
      ......
      }

结果图:
7f4c79817fabaeaa0e909754cfe655e7.0 文件

这里写图片描述

以上本地缓存文件是测试访问腾讯网后生成的文件,很明显文件名经过了md5加密,证实了以上说法。根据对比以上源码及本地缓存内容可证实其操作本地缓存数据流程。

至于最后的DiskLruCache类,它才是真正管理本地缓存目录文件操作,相关方法如下,这里就不再坐一一介绍了,此篇博客的重点还是在第一、二大点。

这里写图片描述





四. 总结

总体而言,Okhttp3源码中有关缓存机制重点的类为以下四个,通过这四个类来总结缓存机制的核心流程:

  • CacheIntercepter
  • CacheStrategy
  • Cache
  • DiskLruCache

核心流程

通过CacheIntercepter类去拦截请求处理缓存相关逻辑,其中使用缓存策略器CacheStrategy类来取出与缓存相关的数据,若缓存中有相应数据则取出,若缓存中数据不存在或过期则重新向服务器发出请求。当前本地有指定缓存时,重新请求的资源数据会被同步到当前本地中,涉及到的同步类是CacheDiskLruCache类,以上是OKhttp3缓存机制的主要流程概括。


到此为止,有关缓存机制的源码分析已经结束,文章主要结合Okhttp3框架源码和图片搭配解析,最后有关Cache、DiskLruCache类仅做介绍,并无深入,有兴趣的读者可自行分析研究,关于这一块我日后再做补充。

这篇文章我写了快一周了,停停写写,源码分析一直都不是一个容易事,想写下来弄清楚更是令人头疼,最后自己动手绘制源码流程图配合文字讲解,总算有个大致的思路。我必须承认这篇文章专业度不够,可能以上某些知识点讲解有误,但是这是尽我目前所能去解析源码到的程度,所以有误还请指教,共同学习~

tips

刚开始分析源码时一定先以整体流程为主,弄清楚之后再从细节(即每个方法的具体实现等)入手!

通常文字讲解某些知识点特别是逻辑类流程时会不够透彻,不妨画个流程图,会让自己的思路更加清晰!

— from lemon Guo

希望对你们有帮助 :)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值