okHttp 原理解析(二)

缓存机制

请求头缓存设置

  • Cache-Control 常见的取值有private、public、no-cache、max-age、no-store、默认是 private。
    在浏览器里面,private 表示客户端可以缓存,public表示客户端和服务器都可以缓存。
  • Last-Modified 服务器告诉浏览器资源的最后修改时间。
  • If-Modified-Since 客户端再次请求服务器时,通过此字段通知服务器上次服务器返回的最后修改时间。
    资源被改动过,则响应内容返回的状态码是200;资源没有修改,则响应状态码为304,告诉客户端继续使用cache。
  • Etag 服务响应请求时,告诉客户端当前资源在服务器的唯一标识
  • If-None-Match 客户端再次请求服务器时,通过此字段通知服务器上次服务器返回的数据标识。
    同修改过返回200,可以使用cache 返回304.

CacheStrategy类

CacheStrategy 根据输出的networkRequest和cacheResponse的值是否为null给出不同的策略

networkRequest cacheResponse result 结果
null null only-if-cached (表明不进行网络请求,且缓存不存在或者过期,一定会返回503错误)
null non-null 不进行网络请求,直接返回缓存,不请求网络
non-null null 需要进行网络请求,而且缓存不存在或者过去,直接访问网络
non-null non-null Header中包含ETag/Last-Modified标签,需要在满足条件下请求,还是需要访问网络

Cachestrategy 通过如下方式构建

CacheStrategy strategy = new CacheStrategy.Factory(
            now, 
            chain.request(), 
            cacheCandidate)
                .get();
    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();
        //获取cacheReposne中的header中值
        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);
          }
        }
      }
    }

    public CacheStrategy get() {
      //获取当前的缓存策略
      CacheStrategy candidate = getCandidate();
     //如果是网络请求不为null并且请求里面的cacheControl是只用缓存
      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
        //使用只用缓存的策略
        return new CacheStrategy(null, null);
      }
      return candidate;
    }

    private CacheStrategy getCandidate() {
      //如果没有缓存响应,返回一个没有响应的策略
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }
       //如果是https,丢失了握手,返回一个没有响应的策略
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }
     
      // 响应不能被缓存
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }
     
      //获取请求头里面的CacheControl
      CacheControl requestCaching = request.cacheControl();
      //如果请求里面设置了不缓存,则不缓存
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, 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());
      }
       //响应支持缓存
       //持续时间+最短刷新时间<上次刷新时间+最大验证时间 则可以缓存
      //现在时间(now)-已经过去的时间(sent)+可以存活的时间<最大存活时间(max-age)
      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());
      }
    
      //如果想缓存request,必须要满足一定的条件
      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 {
        //没有条件则返回一个定期的request
        return new CacheStrategy(request, null); 
      }
      
      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      //返回有条件的缓存request策略
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }

CacheInterceptor 类

负责将Request和Response 关联的保存到缓存中。客户端和服务器根据一定的机制(策略CacheStrategy ),在需要的时候使用缓存的数据作为网络响应,节省了时间和宽带。

 //CacheInterceptor.java
 @Override 
 public Response intercept(Chain chain) throws IOException {
    //如果存在缓存,则从缓存中取出,有可能为null
    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);
    }
    //缓存策略不为null并且缓存响应是null
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }
     //禁止使用网络(根据缓存策略),缓存又无效,直接返回
    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(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }
     //缓存有效,不使用网络
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    //缓存无效,执行下一个拦截器
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }
     //本地有缓存,根据条件选择使用哪个响应
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response 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();

        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
     //使用网络响应
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
       //缓存到本地
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
        }
      }
    }

    return response;
  }

大致流程如下:

  1. 如果配置缓存,则从缓存中取一次
  2. 获取缓存策略
  3. 根据缓存策略获取缓存
  4. 没有网络并且缓存为空,直接返回
  5. 没有网络,直接根据缓存的response返回
  6. 执行下一个拦截器
  7. 存在缓存,根据response的相应头选择缓存
  8. 不存在缓存,直接使用网络 response
  9. 根据缓存策略缓存到本地

Cache

public final class Cache implements Closeable, Flushable {
  final InternalCache internalCache = new InternalCache() {
    @Override public @Nullable Response get(Request request) throws IOException {
      return Cache.this.get(request);
    }

    @Override public @Nullable 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);
    }

    @Override public void trackConditionalCacheHit() {
      Cache.this.trackConditionalCacheHit();
    }

    @Override public void trackResponse(CacheStrategy cacheStrategy) {
      Cache.this.trackResponse(cacheStrategy);
    }
  };
  Cache(File directory, long maxSize, FileSystem fileSystem) {
    this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
  }
}
  DiskLruCache(FileSystem fileSystem, File directory, int appVersion, int valueCount, long maxSize,
      Executor executor) {
    this.fileSystem = fileSystem;
    this.directory = directory;
    this.appVersion = appVersion;
    this.journalFile = new File(directory, JOURNAL_FILE);
    this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
    this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
    this.valueCount = valueCount;
    this.maxSize = maxSize;
    this.executor = executor;
  }

DiskLruCache 内部类

Entry 实际用于存储的缓存数据的实体类,每一个url对应一个Entry实体。同时,每个Entry对应两个文件,key.1存储的是Response的headers,key.2文件存储的是Response的body
Snapshot 一个Entry对象一一对应一个Snapshot对象
Editor 编辑entry类的

初始化

DiskLruCache包含三个日志文件,在执行任何成员函数之前,都需要 initialize() 方法先进行初始化,虽然都调用,但整个生命周期只会被执行一次。

在执行 readJournalLine () 的时候我们会根据不同的头部做出不同的操作

  1. 如果是CLEAN的话,对这个entry的文件长度进行更新
  2. 如果是DIRTY,说明这个值正在被操作,还没有commit,于是给entry分配一个Editor。
  3. 如果是READ,说明这个值被读过了,什么也不做。
    journal 文件
libcore.io.DiskLruCache // MAGIC
1 // VERSION
100 // appVersion
2 // valueCount 每个entry的 value 数量

CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
DIRTY 335c4c6028171cfddfbaae1a9c313c52
CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
REMOVE 335c4c6028171cfddfbaae1a9c313c52
DIRTY 1ab96a171faeeee38496d8b330771a7a
CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
READ 335c4c6028171cfddfbaae1a9c313c52
READ 3400330d1dfc7f3f7f4b8d4d803dfcf6

在执行 rebuildJournal () 的时候

  1. 获取一个写入流,将lruEntries集合中的Entry对象写入tmp文件中,根据Entry的currentEditor的值判断是CLEAN还是DIRTY,来决定写入该Entry的key。如果是CLEAN还需要写入文件的大小bytes。
  2. 把journalFileTmp更名为journalFile
  3. 将journalWriter跟文件绑定,通过它来向journalWrite写入数据,最后设置一些属性即可。

其实 rebuild 操作是以lruEntries为准,把DIRTY和CLEAN的操作都写回到journal中。其实这个操作没有改动真正的value,只不过重写了一些事务的记录。事实上,lruEntries和journal文件共同确定了cache数据的有效性。lruEntries是索引,journal是归档。

总结:

  • 通过LinkedHashMap实现LRU替换
  • 通过本地维护Cache操作日志保证Cache原子性与可用性,同时为防止日志过分膨胀定时执行日志精简。
  • 每一个Cache项对应两个状态副本:DIRTY,CLEAN。CLEAN表示当前可用的Cache。外部访问到cache快照均为CLEAN状态;DIRTY为编辑状态的cache。由于更新和创新都只操作DIRTY状态的副本,实现了读和写的分离。
  • 每一个url请求cache有四个文件。首先是两个状态(DIRY,CLEAN),而每个状态又对应两个文件:一个(key.0, key.0.tmp)文件对应存储meta数据,一个(key.1, key.1.tmp)文件存储body数据。

CallServerInterceptor

连接与请求

OkHttp 中,ConnectionSpec用于描述HTTP数据经由socket时的socket连接配置。由 OkHttpClient 管理。

还提供了ConnectionSpecSelector,用以从ConnectionSpec几个中选择与SSLSocket匹配的ConnectionSpec,并对SSLSocket做配置操作。

在RetryAndFollowUpInterceptor这个拦截器中,需要创建Address,从OkHttpClient中获取ConnectionSpec集合,交给Address配置。
接着在ConnectInterceptor 这个拦截器中,newExchange() -> find() -> findHealthyConnection() -> findConnection() -> connect() 的时候,ConnectionSpec集合就会从Address中取出来,用于构建连接过程。

接着往下是 connect() -> establishProtocol() -> connectTls() -> configureSecureSocket() ->OkHttpClient.apply() -> supoortedSpec() ,这就是重新构建一个兼容的 ConnectionSpec,并配置到 SSLSocket 上

请求头
  @Override public void writeRequestHeaders(Request request) throws IOException {
    String requestLine = RequestLine.get(
        request, streamAllocation.connection().route().proxy().type());
    writeRequest(request.headers(), requestLine);
  }
 
  public void writeRequest(Headers headers, String requestLine) throws IOException {
    if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
    sink.writeUtf8(requestLine).writeUtf8("\r\n");
    for (int i = 0, size = headers.size(); i < size; i++) {
      sink.writeUtf8(headers.name(i))
          .writeUtf8(": ")
          .writeUtf8(headers.value(i))
          .writeUtf8("\r\n");
    }
    sink.writeUtf8("\r\n");
    state = STATE_OPEN_REQUEST_BODY;
  }
请求体
@Override 
public Sink createRequestBody(Request request, long contentLength) {
    if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
      // Stream a request body of unknown length.
      return newChunkedSink();
    }
    ...
}
  private final class ChunkedSink implements Sink {
    private final ForwardingTimeout timeout = new ForwardingTimeout(sink.timeout());
    private boolean closed;

    ChunkedSink() {
    }

    @Override public Timeout timeout() {
      return timeout;
    }

    @Override public void write(Buffer source, long byteCount) throws IOException {
      if (closed) throw new IllegalStateException("closed");
      if (byteCount == 0) return;

      sink.writeHexadecimalUnsignedLong(byteCount);
      sink.writeUtf8("\r\n");
      sink.write(source, byteCount);
      sink.writeUtf8("\r\n");
    }

    @Override public synchronized void flush() throws IOException {
      if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf.
      sink.flush();
    }

    @Override public synchronized void close() throws IOException {
      if (closed) return;
      closed = true;
      sink.writeUtf8("0\r\n\r\n");
      detachTimeout(timeout);
      state = STATE_READ_RESPONSE_HEADERS;
    }
  }

写完请求头和请求体会调用 sink.flush()

接下来是读取相应头和响应体

  @Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
    if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
      throw new IllegalStateException("state: " + state);
    }

    try {
      StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());

      Response.Builder responseBuilder = new Response.Builder()
          .protocol(statusLine.protocol)
          .code(statusLine.code)
          .message(statusLine.message)
          .headers(readHeaders());

      if (expectContinue && statusLine.code == HTTP_CONTINUE) {
        return null;
      }

      state = STATE_OPEN_RESPONSE_BODY;
      return responseBuilder;
    } catch (EOFException e) {
      // Provide more context if the server ends the stream before sending a response.
      IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
      exception.initCause(e);
      throw exception;
    }
  }

  @Override public ResponseBody openResponseBody(Response response) throws IOException {
    Source source = getTransferStream(response);
    return new RealResponseBody(response.headers(), Okio.buffer(source));
  }

你可能感兴趣的:(okHttp 原理解析(二))