OkHttp3 使用详解及网络连接缓存的处理机制

本文详细介绍了OkHttp3的使用,包括创建OkHttpClient,构建Request,同步和异步发送请求。重点讨论了拦截器,特别是CacheInterceptor的工作原理,如缓存有效性判断、再验证机制等,帮助理解OkHttp3的网络连接缓存处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 创建 OkHttpClient 对象


可以直接新建,也可以用建造者模式建造出来。直接新建时,其实也是使用建造者设置了默认的请求参数。

OkHttpClient client = new OkHttpClient();

OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(5, TimeUnit.SECONDS)
        .writeTimeout(1000, TimeUnit.SECONDS)
        .readTimeout(1000, TimeUnit.SECONDS)
        .build();
功能函数注释
添加应用拦截器addInterceptor(Interceptor)最接近应用的拦截器
添加网络拦截器addNetworkInterceptor(Interceptor)接近网络的拦截器
设置缓存对象cache(Cache cache)使用 DiskLruCache 实现

okhttp3.Cache 构造时只需要指定缓存目录和大小即可。

网络拦截器实例:给返回的响应添加缓存过期时间

private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Response originResponse = chain.proceed(chain.request());
        // 5 分钟后过期
        CacheControl.Builder builder = new CacheControl.Builder()
                .maxAge(5, TimeUnit.MINUTES);

        return originResponse.newBuilder()
                .header("Cache-Control", builder.build().toString())
                .build();
    }
};


2. 准备好 Request 对象


通常用建造者模式来建造。

new Request.Builder().build();
功能函数注释
添加 URLurl(String url)
GET 请求get()
POST 方式提交表单post(RequestBody body)
替换请求头header(String name, String value)先删除 name 对应的原有的字段对,再添加
添加请求头addHeader(String name, String value)直接往 ArrayList 中添加两个 String

表单的构建

new FormBody.Builder().build()
功能函数注释
添加表单项add(String name, String value)没有 encode 的字符串
添加表单项addEncoded(String name, String value)已经 encode 的字符串


3. 发送请求


OkHttpClient 实例先用 newCall(Request) 方法对请求再一次包装,返回 Call 对象,表示请求已经准备好。然后有同步和异步两种方式来发送请求:

同步方式:

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

异步方式:

Response response = client.newCall(request).enqueue(callback);

异步方式会用一个 CachedThreadPool 线程池来管理异步任务,任务一提交就马上在非核心线程中执行,非核心线程的空闲存活期是 60s。

  1. enqueue 方法会传入一个 okhttp3.Callback 对象,需要注意的是回调时还是在子线程。
new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        // 通过runOnUiThread()方法回到主线程处理逻辑
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                closeProgressDialog();
                Toast.makeText(getContext(), "加载失败", Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {

    }
}
  1. enqueue 方法中,callback 被包装成实现了 Runnable 接口的 AsyncCall 对象,传入 OkHttpClient 的 Dispatcher 。Dispatcher 会使用线程池执行 AsyncCall 的 execute 方法,最终的执行结果回调 Callback。
@Override protected void execute() {
  boolean signalledCallback = false;
  try {
    Response response = getResponseWithInterceptorChain();
    if (retryAndFollowUpInterceptor.isCanceled()) {
      signalledCallback = true;
      responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
    } else {
      signalledCallback = true;
      responseCallback.onResponse(RealCall.this, response);
    }
  }


4. 拦截器


不论是 execute 方法还是 enqueue 方法发送链接请求,最终都会调用 getResponseWithInterceptorChain 方法。

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 (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));

  Interceptor.Chain chain = new RealInterceptorChain(interceptors,
                null, null, null, 0,
                originalRequest, this, eventListener,
                client.connectTimeoutMillis(),
                client.readTimeoutMillis(),
                client.writeTimeoutMillis());

  return chain.proceed(originalRequest);
}

在请求往外发送时一次经过7个拦截器,第2~5个可以认为是 OkHttpCore 核心。

序号名称注释
1应用拦截器
interceptors
由用户在创建 OkHttpClient 时设置
2重试和重定向拦截器
RetryAndFollowUpInterceptor
1.网络请求出现异常时,如果满足重试条件就发送重试请求;
2.如果网络响应包含重定向信息,就创建重定向请求并发送
3桥接拦截器
BridgeInterceptor
1.深加工用户传递来的请求,设置默认请求头;
2. 用户没有设置时默认采用 gzip 压缩解压数据
4缓存拦截器
CacheInterceptor
缓存的查找和保存
5连接拦截器
ConnectInterceptor
给网络请求提供一个连接,之后拦截器中 chain.connection() 才不为 null
6网络拦截器
networkInterceptors
由用户在创建 OkHttpClient 时设置
7呼叫服务器拦截器
CallServerInterceptor
网络请求最终从这里发送出去,并包装响应对象


5. CacheInterceptor 详解


网络请求中与缓存有关的头域:

序号名称注释
1Cache-Control:max-age缓存的推荐保质期
2Cache-Control:max-stale缓存超过保质期多久仍可接受
3Cache-Control:min-fresh缓存距离推荐保质期还有多久就认为缓存不新鲜了
4Cache-Control:immutable缓存永不过期
5Cache-Control:only-if-cached只允许使用当前缓存数据,没有缓存就只能返回 504 响应
6Cache-Control:No-Cache不希望使用缓存
7Age响应对象在代理缓存中存在的时间,以秒为单位
8Date当前的GMT时间
9ETag对于某个资源的某个特定版本的一个标识符
10Expires超过该时间则认为此回应已经过期
11Last-Modified服务器资源的最后修改时间

相关响应头:

序号名称注释
1200 OK合适的资源作为响应体传回客户端
2301 Moved Permanently所请求的 URL 资源路径已经改变,
新的 URL 在响应的 Location 头字
3304 Not Modified所请求的内容距离上次访问并没有
变化,浏览器缓存有效
4404 Not Found服务器找不到所请求的资源
5504 Gateway TImeout服务器作为网关不能从上游服务器
得到响应返回给客户端

下面将介绍缓存有效性相关的 3 个概念:

  1. 新鲜度检测
  2. 在验证
  3. 在验证命中

在使用缓存将 url 响应资源的副本存储在本地时,当我们再次对该 url 资源发起请求时,可以快速从本地存储中获取该 url 资源,而不需要重新发起网络连接。

但是,服务器的 url 资源可能在一定时间后被修改,因此我们不能一直使用缓存资源,在一定时间之前,认为可以使用缓存资源,在这个时间之后认为不能再使用缓存资源,需要重新请求网络资源。这个条件的判断就是 新鲜度检测。就像我们在吃食品之前要看看它有没有过期。

url 资源缓存超过一定时间,我们也不会直接重新请求 url 资源,而是去服务器查看资源是否已经发生改变,这就叫 再验证

如果服务器发现 url 资源没有改变,就返回 304 Not Modified,并不在返回对应实体,这就叫 再验证命中,此时我们可以继续使用 url 资源缓存。如果发生了变化,则返回 200 OK,并将改变后的 url 资源返回。

具体实现:

  1. 新鲜度检测

    http1.1 规范中,通过响应首部的 Cache-Control:max-age 和 Last-Modified 计算出绝对时间。http1.0 规范中,响应首部 Expires 的值就是绝对时间。

  2. 再验证

    超过绝对时间后要进行再验证,需要根据响应首部具体的规范来进行 条件请求,通常有 5 种条件请求首部。

序号名称注释
1If-Modified-Since和响应首部 Last-Modified 配合使用,询问
服务器 url 资源的最后修改时间是否变化,
没有的话就返回 304 未修改
2If-None-Match和响应首部 Etag 配合使用,Etag 相当于服务器
对 url 资源定义的粗粒度的版本号,即使资源修
改了,但如果版本号没有变化,仍然认为再验证
命中。
3If-Unmodified-Since
4If-Range
5If-Match

在 CacheInterceptor 源码中,首先获取缓存的 Response 对象(可能为 null)。然后用缓存的 Response,以及当前的时间和 Request 构建 CacheStrategy 对象。

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

CacheStrategy 有 networkRequestcacheResponse 两个变量,根据工厂方法传入的三个参数,get 方法中有 5 种判断条件来设置它们的值。

时序条件networkRequestcacheResponse
1 没有缓存时
2. 请求采用 https 但是缓存没有进行握手的数据
3. 缓存不应该被保存(保留了一些不应该缓存的数据)
4. 请求添加了 Cache-Control:No-Cache
或者一些条件请求首部,说明不希望使用缓存
传入的 resquestnull
2缓存响应首部包含 Cache-Control:immutable
(不属于 http 协议),说明资源不会改变
null传入的缓存响应
3新鲜度验证通过null传入的缓存响应
(可能会添加一些首部)
4新鲜度验证不通过,使用 Etag 或 Last-Modified
或 Date 首部构造条件请求并返回
条件请求传入的缓存响应
5新鲜度验证不通过,且缓存响应没有 Etag、
Last-Modified 和 Date 中的任何一个
传入的 resquestnull
6上述 5 种情况中 networkRequest 不为空时,若
请求通过 Cache-Control:only-if-cached 只允许
我们使用当前缓存
nullnull

在 CacheInterceptor 的 intercept 方法中,会根据创建的 CacheStrategy 对象的两个变量的值来进行处理。

时序networkRequestcacheResponse处理方法
1nullnull直接返回 504 Unsatisfiable Request (only-if-cached)
响应。
2null非 null说明缓存有效,直接返回 cacheResponse
3非 nullnull 或
非 null
说明需要向网络发送请求(原始 request 或新鲜度验证后
的条件请求):
1. 如果有缓存数据,在获得再验证的响应后,使用 cache
的 update 方法更新缓存;
2. 如果没有缓存数据,判断请求是否可以被缓存,可以的
话就使用 cache 的 put 方法缓存下来


参考资料


  1. HTTP消息头(HTTP headers)-常用的HTTP请求头与响应头
  2. HTTP 必知必会的那些
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值