OkHttp缓存篇

发布时间:2022-07-01 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了OkHttp缓存篇脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

文章目录

  • OkHttp
      • 连接拦截器篇
  • Demo代码
  • 流程图
  • 拦截器 Interceptor
      • RetryAnDFollowUpInterceptor
      • bridgeinterceptor
      • CacheInterceptor
      • ConnectInterceptor
      • networkInterceptors
      • CallServerInterceptor
  • `CacheInterceptor`缓存详解
      • Cache.java
      • CacheStrategy.java 缓存策略
  • okhttP3.Dispatcher 异步请求调度
  • Http Header配置知识
      • 查看http请求头的方式
      • 常见配置项
      • 缓存 Cache-Control
      • 协商缓存`Last-Modify/If-Modify-since`,`If-None-Match/ETag`
      • Range和Content-Range
      • User-Agent
      • SSL加密方式配置
  • RealCall类
  • http2.0
  • Java方法
  • 未研究

OkHttp

  • 官网文档: https://square.gIThub.io/okhttp/
  • 设计模式: 建造者模式、责任链模式
  • 对象池,连接池

连接拦截器篇

  • https://blog.csdn.net/followYouself/article/details/121086869

Demo代码

  • addInterceptor:应用层拦截器,在网络请求前拦截。addNetworkInterceptor:网络层拦截器,在发起网络请求后,进行拦截。
  • callTimeout:本次请求的总体超时时间。包括connect、write、read等阶段时间。
  • connectTimeout:连接阶段超时时间,默认10秒。配置tcp层socket参数,java.net.Socket#connect(java.net.SocketAddress, int)。参考OkhttpRealConnection#connectSocket
  • readTimeout:socket read函数超时时间,默认10秒。配置tcp层socket参数,java.net.Socket#setSoTimeout。参考Okhttp源码RealConnection#connectSocket
  • writeTimeout:连接阶段超时时间,默认10秒。
// 定义一个拦截器
Interceptor interceptor = chain -> {
    Request request = chain.request();

    long t1 = System.nanoTime();
    LOG.i(TAG, String.format("Send request %s on %s%n%s", request.url(), chain.connection(), request.headers()));

    Response response = chain.PRoceed(request);

    long t2 = System.nanoTime();
    Log.i(TAG, String.format("Received response for %s in %.1fms%n%s",
            response.request().url(), (t2 - t1) / 1e6d, response.headers()));

    return response;
};
OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(interceptor) // 应用层拦截器,在网络连接生效
        .addNetworkInterceptor(interceptor) // 网络请求前的拦截器,在网络连接后生效
        .cache(new Cache(new File("/data/data/com.test.http/cache", "http_cache"), 50 * 1024 * 1024)) // 50 MB
        .callTimeout(5, TimeUnit.SECONDS)
        .connectTimeout(5, TimeUnit.SECONDS)
        .readTimeout(5, TimeUnit.SECONDS)
        .writeTimeout(5, TimeUnit.SECONDS)
        .eventListener(new EventListener(){})
        .build();
CacheControl cacheControl = new CacheControl.Builder()
        .noCache() // 即使有缓存,每次也都发起网络请求,收到304相应后,使用缓存。否则更新缓存。
        .maxAge(60, TimeUnit.SECONDS)
        // .onlyIfcached() //
        .build();
Request request = new Request.Builder()
        .url("https://github.COM/square/okhttp")
        .cacheControl()
        .build();

client.newCall(request).enqueue(new Callback() {
    @override
    public void onFailure(Call call, IOException e) {
        e.printStackTrace();
        Log.i(TAG, "onFailure " + call.request());
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Log.i(TAG, "onResponse " + call.request() + ", Response Content" + response);
    }
});

流程图

@H_297_777@OkHttp缓存篇

拦截器 Interceptor

OkHttp缓存篇

  • 官网文档: https://square.github.io/okhttp/interceptors/
  • 缓存拦截器: https://juejin.cn/post/6845166891476992008
  • 无论是异步请求还是同步请求都会通过RealCall#getResponseWithInterceptorChain这个函数来遍历拦截器,发起网络请求。拦截器是按照添加顺序进行遍历的。
  • 拦截器的遍历执行顺序F1a;client.interceptors()RetryAndFollowUpInterceptorBridgeInterceptorCacheInterceptorConnectInterceptorclient.networkInterceptors()CallServerInterceptor
  • okhttp3.Interceptor.Chain接口的唯一实现类是RealInterceptorChain。通过该类的RealInterceptorChain#proceed(okhttp3.Request)实现遍历拦截器的操作。
// okhttp3.RealCall#getResponseWithInterceptorChain
Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors()); // 自定义应用拦截器
  interceptors.add(new RetryAndFollowUpInterceptor(client)); // 重试、重定向拦截器
  interceptors.add(new BridgeInterceptor(client.cookiejar())); // 桥接拦截器,主要是修改header参数、gzip压缩
  interceptors.add(new CacheInterceptor(client.internalCache())); // 缓存
  interceptors.add(new ConnectInterceptor(client)); // 待研究
  if (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors()); // 待研究
  }
  interceptors.add(new CallServerInterceptor(forWebSocket)); // 待研究

  // okhttp3.Interceptor#intercept方法chain参数的实际类型
  Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
      originalRequest, this, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());

  boolean calledNoMoreExchanges = false;
  try {
    Response response = chain.proceed(originalRequest);
    if (transmitter.isCanceled()) {
      closeQuietly(response);
      throw new IOException("Canceled");
    }
    return response;
  } catch (IOException e) {
    calledNoMoreExchanges = true;
    throw transmitter.noMoreExchanges(e);
  } finally {
    if (!calledNoMoreExchanges) {
      transmitter.noMoreExchanges(null);
    }
  }
}
  • 通过index的累加,实现对interceptors的遍历。设计模式的责任链模式
// okhttp3.internal.http.RealInterceptorChain#proceed(okhttp3.Request, okhttp3.internal.connection.Transmitter, okhttp3.internal.connection.Exchange)

  public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      throws IOException {

    // 省略异常检查代码.....

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // 省略对Response的异常检查代码.....

    return response;
  }

RetryAndFollowUpInterceptor

  • 默认情况下是会进行重试。默认的配置参数是 OkHttpClient.Builder#retryOnConnectionFailure(boolean)
  • 重试、重定向拦截器
  • 重定向函数 RetryAndFollowUpInterceptor#followUpRequest。做多重定向 20次

BridgeInterceptor

  • 相对还比较简单,主要是想Http Header里面添加了一些字段配置。
  • 最主要的是自动添加了gzip压缩配置。同时对收到的数据解压缩
  • 配置了Connection:Keep-Alive代表需要长连接
// okhttp3.internal.http.BridgeInterceptor#intercept
if (userRequest.header("Connection") == null) {
  requestBuilder.header("Connection", "Keep-Alive");
}

// If we add an "Accept-Encoding: gzip" header field we're responsible for also decomPressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
  transparentGzip = true;
  requestBuilder.header("Accept-Encoding", "gzip");
}

CacheInterceptor

  • 参考CacheInterceptor缓存详解章节。

ConnectInterceptor

  • 参考连接拦截器篇:https://blog.csdn.net/followYouself/article/details/121086869

networkInterceptors

  • 自定义的网络解析拦截器。
  • 在和服务器建立连接之后,在真正的发起网络请求之前进行拦截。

CallServerInterceptor

  • 实际使用Okio向服务器进行交互,进行request请求,接收response
  • Http2相关的请求类 Http2Stream.java, Http2Reader,Http2Writer

CacheInterceptor缓存详解

  • 默认情况下,不开启任何缓存。开启缓存需要通过OkHttpClient.Builder#cache(@Nullable Cache cache)方法进行配置,指定缓存File文件路径。
  • okhttp只会对get请求进行缓存。
  • url作为缓存的key信息。
  • CacheControl一些关键配置no-cacheno-Storemax-ageonly-if-cachedmax-stale。参考下文Http header章节
  • 如果服务端不支持缓存配置,也可以source端实现缓存。可以通过addNetworkInterceptor,在Response中增加cache-control,配置max-age(优先级高),Last-Modify(优先级低)等参数。参考源码CacheStrategy.Factory#computeFreshnessLifetime
  • 官网: https://square.github.io/okhttp/caching/
  • 参考资料:https://juejin.cn/post/6850418120729985038。这个wiki有个问题,混淆了noCachenoStore

Cache.java

  • getputremoveupdate分别对应缓存的读、写、删除、更新操作。
  • diskLruCache封装了缓存读写的能力,利用Okio的读写能力。参考资料:https://blog.csdn.net/zwlove5280/article/details/79916662
  • 缓存的键值是request的url。参考okhttp3.Cache#key方法。
  • Response的Header信息存储在.0文件,body信息存储在.1文件,所有操作的日志记录在journal文件。
  • 如何读写的细节以及DiskLruCache细节没有研究。

CacheStrategy.java 缓存策略

  • 根据request请求和缓存中的Response决定后续的网络请求步骤。关键方法是CacheStrategy.Factory#get()CacheStrategy.Factory#getCandidate()
  • 这个类有两个成员变量 networkRequest 不为空,表示需要发起网络请求。null,则表示不需要网络请求。cacheResponse不为null,该缓存需要验证或者直接作为结果。为null,表示不使用缓存。
  • 需要特别注意的是,如果networkRequest不为空,同时request也配置了only-if-cached,那么会报504错误, Unsatisfiable Request (only-if-cached)
  • CacheStrategy#isCacheable,首先判断Response的状态码是否支持缓存,然后在检查ResponseRequest的header,如果配置了no-store,那么不支持缓存。
  • 缓存策略 之 CacheStrategy.Factory#getCandidate函数。代码中的注释 1,2,3分别与下面对
  1. 如果在cache中没有找到cacheResponse,那么需要网络请求。
  2. 如果是https请求,但是cacheResponse中没有handshake,那么需要网络请求。
  3. isCacheable判断cacheResponse中的状态码是否支持缓存,同时判断request请求是否配置了noStore。如果配置了noStore,那么禁止使用缓存。
  4. 如果请求配置了noCache或者配置了If-Modified-Since或者配置了If-None-Match,那么直接发起网络请求。根据CacheInterceptor.java代码,网络请求完成后,如果cacheResponse不为空,并且收到304状态码,那么使用cacheResponse作为请求结果。
  5. 根据requestmaxagemin-freshmaxStale,以及根据cacheResponsesentRequestatMillisreceivedResponseAtMillisservedDatemust-revalidate等配置,计算cacheResponse是否过期,是否满足本次的request的要求。如果满足,那么直接使用缓存作为网络请求的结果,不发起实际的网络请求。具体的计算细节没研究…
  6. 此处根据时间计算,判断是否使用缓存。ageMillis:cacheResponse从生成到现在的耗时; minFreshMillis:最小新鲜度,距离最终过期的最短时间,request中配置;freshMillis:cacheResponserequest配置的max-age中的较小值;maxStaleMillis在超过max-age后,仍然可以接受的时间,request中配置。
  7. 在上述条件均没有满足,缓存也过期的情况下,依次 判断cacheResponse是否携带了ETagLast-ModifiedDate等字段,转换为If-None-MatchIf-Modified-Since字段,添加到request header中。
  8. 如果networkRequest不为空,同时request也配置了only-if-cached,根据CacheInterceptor.java代码,会上报504错误, Unsatisfiable Request (only-if-cached)

    OkHttp缓存篇

// okhttp3.internal.cache.CacheStrategy.Factory
/**
 * Returns a strategy to satisfy {@code request} using the a cached response {@code response}.
 */
public CacheStrategy get() {
  CacheStrategy candidate = getCandidate(); // 根据request请求和缓存中的Response决定后续的网络请求步骤
  // 禁止同时有networkRequest 和 onlyIfCached配置。
  if (candidate.networkRequest != null &amp;& request.cacheControl().onlyIfCached()) { // 注释8
    // We're forbidden From using the network and the cache is insufficient.
    return new CacheStrategy(null, null); // 此处为空,会报`504`错误。
  }
  return candidate;
}

/** Returns a strategy to use assuming the request can use the network. */
private CacheStrategy getCandidate() {
  // No cached response.
  if (cacheResponse == null) { // 注释1
    return new CacheStrategy(request, null);
  }

  // Drop the cached response if it's missing a required handshake.
  if (request.isHttps() && cacheResponse.handshake() == null) {  // 注释2
    return new CacheStrategy(request, null);
  }

  // If this response shouldn't have been stored, it should never be used
  // as a response source. This check should be redundant as long as the
  // PErsistence store is well-behaved and the rules are constant.
  if (!isCacheable(cacheResponse, request)) {  // 注释3
    return new CacheStrategy(request, null);
  }

  CacheControl requestCaching = request.cacheControl(); // 获取请求中的cacheControl配置
  if (requestCaching.noCache() || hasConditions(request)) { // 注释4
    return new CacheStrategy(request, null);
  }

  CacheControl responseCaching = cacheResponse.cacheControl();  // 注释5
  long ageMillis = cacheResponseAge(); // 计算缓存从生成开始,已经度过了多长时间
  long freshMillis = computeFreshnessLifetime(); // 返回Response配置的max-age,最大可存活的生命时间

  if (requestCaching.maxAgeSeconds() != -1) {
    // cacheResponse中配置的max-age 和 request中配置的max-age中,取最小的一个值。
    // 此处就最终计算出来允许Response有效时间
    freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
  }

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

  long maxStaleMillis = 0;
  // 计算可以接受的超期时间
  if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
    maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
  }

  // 如果缓存没有过期,满足当前request的要求,那么直接使用缓存作为结果。
  if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { // 注释6
    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());
  }

  // Find a condition to add to the request. If the condition is satisfied, the response body
  // will not be transmitted.
  String conditionName;
  String conditionValue;
  if (etag != null) {   // 注释7
    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

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

okhttp3.Dispatcher 异步请求调度

  • maxRequests最大并发请求数量,默认64。maxRequestsPerHost 每个主机host支持的最大并发请求数量,默认5。可配置。
  • 包含一个线程池executorService,用于异步的网络请求调度。默认的配置为核心线程为0,最大线程数不限制,存活时间60秒。
// okhttp3.Dispatcher#executorService
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
    new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));

Http Header配置知识

查看http请求头的方式

  • 浏览器内,F12
  • F5 刷新网页
  • NetWork选项卡,Doc选项卡,在Name中选择一次请求
  • 选择Headers选项卡。(和Header选项卡并列的有Preview、Response、Cookies)

常见配置项

  • Accept-Encoding:gzip 代表压缩编码数据
  • Connection:Keep-Alive代表需要长连接

缓存 Cache-Control

  • no-cache:资源是可以被客户端缓存的,代理服务器不缓存。但是每次请求都需要先到服务端验证资源是否有效。相关配置参考Last-Modify / If-Modify-Since
  • no-store:禁止任何形式的缓存。客户端和服务端均可进行配置生效。
  • max-age:资源可以被缓存的时间,单位秒。max-age会覆盖掉expires。超期后,访问服务器校验有效性
  • s-maxage:设置代理服务器缓存的最大的有效时间,单位秒。s-maxage会覆盖掉max-age
  • public:表示当前的请求是一种通用的业务数据,客户端、代理服务器、中间节点服务器都可以缓存这些数据。
  • private:默认值。表示当前的操作是和具体用户强相关的特殊行为,不应该在代理类服务器、中间节点服务器等进行缓存。因为缓存并没有很大的意义。
  • only-if-cached:不进行网络请求,完全只使用缓存.如果缓存不命中,返回504错误。并且此处的优先级要高于no-cache,肯定不会发起网络请求了。
  • must-revalidate: 资源一旦过期,则必须向服务器发起请求确认资源有效性。如果无法访问服务器,则上报 504 Gateway Timeout。优先级高于max-stale,设置之后max-stale变为无效。
  • max-stale:客户端要求缓存代理该时间内(默认不限时间)的资源无论缓存有没有过期都返回给客户端。这个参数表示业务可以接受的响应的过期时间。
  • no-transform:缓存代理不可更改媒体类型,这样可以止压缩图片、压缩资源的操作
  • min-fresh:距离缓存Response过期(max-agemax-stale之和)剩余的最小时间,保证取到缓存不会在短时间(min-fresh)内超期无效。比如max-age=100, max-stale=500min-fresh=200。那么Responsemax-age + max-stale和是600,由于配置了min-fresh,那么要求在使用缓存是,必须保证距离600这个最终过期时间点,保留200的新鲜度。600 - 200 = 400,那么也就是缓存的Response从缓存开始到现在nowTime最多度过了400的时间长度,才可以使用。
  • 参考资料 https://segmentfault.com/a/1190000022336086

协商缓存Last-Modify/If-Modify-SinceIf-None-Match/ETag

  • etag的校验优先级高于Last-Modify
  • Last-Modify:在服务器的Response中携带,表示业务数据上次被修改的时间
  • If-Modify-Since:当缓存过期时,在客户端request请求时携带,服务器上检查request的请求时间,并校验服务器资源是否被修改。如果被修改,那么返回最新数据,并返回HTTP 200 OK。如果资源没有修改,那么仅仅返回状态码HTTP 304
  • etag:服务器Response携带的资源tag。资源的任何修改都会导致tag的改变,如果tag不变,是可以表示资源数据没有变化的。
  • If-None-Match:当缓存过期时,客户端request的请求中携带,携带的是缓存中Response的tag值。同样是根据tag判断资源数据是否被修改,相应客户端请求。

Range和Content-Range

  • 范围参数,可以制定要从服务器获取文件的范围。
  • 设计目的:用于断点续传。比如下载大文件时,网络突然中断,恢复网络时,可以继续下载内容。

User-Agent

  • 代表用户行为的程序软件。比如,网页浏览器就是一个“帮助用户获取、渲染网页内容并与之交互”的用户代理;子邮件阅读器也可以称作邮件代理。
  • 举例
Mozilla/5.0 (Windows NT 10.0; Win64; x64) Applewebkit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36

SSL加密方式配置

  • 配置函数 OkHttpClient#sslSocketFactory, 默认配置函数OkHttpClient#newSslSocketFactory
  • 分为AndROId平台和java平台,Android平台的配置函数是AndroidPlatform#getSSLContext,源码如下
@Override
public SSLContext getSSLContext() {
  boolean tryTls12;
  try {
    tryTls12 = (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22);
  } catch (NoClaSSDefFoundError e) {
    // Not a real Android runtime; probably RoboVM or MoE
    // Try to load TLS 1.2 explicitly.
    tryTls12 = true;
  }

  if (tryTls12) {
    try {
      return SSLContext.getInstance("TLSv1.2");
    } catch (NoSuchAlgorithmException e) {
      // fallback to TLS
    }
  }

  try {
    return SSLContext.getInstance("TLS");
  } catch (NoSuchAlgorithmException e) {
    throw new IllegalStateException("No TLS provider", e);
  }
}
  • CLEARTEXT是用于不安全配置的http://网址。

RealCall类

OkHttp缓存篇

http2.0

  • http2.0 和http1.0 的区别:http2.0支持多路复用,支持多个请求并发传输。http1.0是顺序执行的。发送一个请求之后,等到响应之后,才能发起下一个请求。
  • http2.0中重要的概念是 帧(frame)流(stream)。每一个流都是有一个独一无二的编号。
  • HTTP2.0 消息头的压缩算法采用 HPACK。
  • http2.0:https://segmentfault.com/a/1190000016975064
  • http2.0:https://juejin.cn/post/6844903935648497678

Java方法

  • java.lang.Thread#holdsLock 判断当前线程是否持有某个锁

未研究

  • 各个版本的http协议区别,协议的细节。
  • Okio开源库作为基础读写库,没有阅读源码。Okio实现原理了解segment机制。
  • DiskLruCache.java 文件缓存方案,没有阅读源码
  • Http2Connection.java http2协议的连接,数据收发,stream使用相关信息

脚本宝典总结

以上是脚本宝典为你收集整理的OkHttp缓存篇全部内容,希望文章能够帮你解决OkHttp缓存篇所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。