Android

关注公众号 jb51net

关闭
首页 > 软件编程 > Android > Android OKHttp拦截器

Android OKHttp拦截器和缓存案例详解

作者:每次的天空

OkHttp的拦截器链是一个有序的拦截器集合,请求和响应会依次经过每个拦截器,这篇文章主要介绍了Android OKHttp拦截器和缓存案例详解,感兴趣的朋友一起看看吧

深入理解 OkHttp 拦截器

1. 拦截器接口详解

Interceptor 接口是自定义拦截器的基础,它仅包含一个抽象方法 intercept。以下是对该方法参数和返回值的详细解释:

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class CustomInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        // chain 包含了当前请求的所有信息以及后续拦截器的处理逻辑
        Request originalRequest = chain.request(); 
        // 可以对原始请求进行修改,例如添加请求头、修改请求方法等
        Request modifiedRequest = originalRequest.newBuilder()
               .header("Custom-Header", "Custom-Value")
               .build();
        // proceed 方法会将修改后的请求传递给下一个拦截器,并返回响应
        Response response = chain.proceed(modifiedRequest);
        // 可以对响应进行处理,例如添加自定义响应头、解析响应体等
        return response.newBuilder()
               .header("Custom-Response-Header", "Custom-Response-Value")
               .build();
    }
}

Chain 参数:Chain 是一个接口,它代表了整个拦截器链。chain.request() 方法可以获取当前的请求对象;

chain.proceed(request) 方法会将请求传递给下一个拦截器,并返回响应。

Response 返回值:intercept 方法必须返回一个 Response 对象,这个对象可以是原始响应,也可以是经过修改后的响应。

2.拦截器链的详细执行流程

整体流程

OkHttp 的拦截器链是一个有序的拦截器集合,请求和响应会依次经过每个拦截器。拦截器链的执行顺序是固定的,如下所示:

1. 用户自定义拦截器(client.interceptors())

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class CustomHeaderInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originalRequest = chain.request();
        Request newRequest = originalRequest.newBuilder()
               .header("Custom-Header", "Custom-Value")
               .build();
        return chain.proceed(newRequest);
    }
}

2. 重试和重定向拦截器(RetryAndFollowUpInterceptor)

3. 桥接拦截器(BridgeInterceptor)

4. 缓存拦截器(CacheInterceptor)

5. 连接拦截器(ConnectInterceptor)

6. 用户自定义网络拦截器(client.networkInterceptors())

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class NetworkLoggingInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        long t1 = System.nanoTime();
        System.out.println(String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));
        Response response = chain.proceed(request);
        long t2 = System.nanoTime();
        System.out.println(String.format("Received response for %s in %.1fms%n%s",
                response.request().url(), (t2 - t1) / 1e6d, response.headers()));
        return response;
    }
}

7. 服务器调用拦截器(CallServerInterceptor)

3.应用拦截器和网络拦截器的区别

应用拦截器

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class ApplicationInterceptorExample {
    public static void main(String[] args) throws IOException {
        CustomInterceptor customInterceptor = new CustomInterceptor();
        OkHttpClient client = new OkHttpClient.Builder()
               .addInterceptor(customInterceptor)
               .build();
        Request request = new Request.Builder()
               .url("https://example.com")
               .build();
        Response response = client.newCall(request).execute();
        System.out.println(response.body().string());
    }
}

网络拦截器

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class NetworkInterceptorExample {
    public static void main(String[] args) throws IOException {
        CustomInterceptor customInterceptor = new CustomInterceptor();
        OkHttpClient client = new OkHttpClient.Builder()
               .addNetworkInterceptor(customInterceptor)
               .build();
        Request request = new Request.Builder()
               .url("https://example.com")
               .build();
        Response response = client.newCall(request).execute();
        System.out.println(response.body().string());
    }
}

4. 拦截器的高级应用场景

缓存控制拦截器

可以创建一个拦截器来动态控制缓存策略,例如根据网络状态或用户设置来决定是否使用缓存。

import okhttp3.*;
import java.io.IOException;
public class CacheControlInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        if (isNetworkAvailable()) {
            // 网络可用时,设置缓存策略为最多缓存 60 秒
            request = request.newBuilder()
                   .header("Cache-Control", "max-age=60")
                   .build();
        } else {
            // 网络不可用时,强制使用缓存
            request = request.newBuilder()
                   .header("Cache-Control", "only-if-cached")
                   .build();
        }
        return chain.proceed(request);
    }
    private boolean isNetworkAvailable() {
        // 实现网络状态检查逻辑
        return true;
    }
}

超时重试拦截器

可以创建一个拦截器来处理请求超时的情况,当请求超时时,自动重试一定次数。

import okhttp3.*;
import java.io.IOException;
public class RetryInterceptor implements Interceptor {
    private static final int MAX_RETRIES = 3;
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = null;
        IOException exception = null;
        for (int i = 0; i < MAX_RETRIES; i++) {
            try {
                response = chain.proceed(request);
                if (response.isSuccessful()) {
                    break;
                }
            } catch (IOException e) {
                exception = e;
            }
        }
        if (response == null) {
            throw exception;
        }
        return response;
    }
}

扩展追问:

如何保证OKHttp 拦截器链中每个拦截器能按预定顺序执行

答:OKHttp 的拦截器顺序就像一场 “接力赛”:

原理:

拦截器的 intercept 方法调用

每个拦截器都实现了 Interceptor 接口,该接口有一个 intercept 方法。在 intercept 方法中,需要调用传入的 Chain 对象的 proceed 方法,将请求传递给下一个拦截器。例如 BridgeInterceptor 的 intercept 方法:

@Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();
    // 处理请求头
    RequestBody body = userRequest.body();
    if (body != null) {
        MediaType contentType = body.contentType();
        if (contentType != null) {
            requestBuilder.header("Content-Type", contentType.toString());
        }
        long contentLength = body.contentLength();
        if (contentLength != -1) {
            requestBuilder.header("Content-Length", Long.toString(contentLength));
            requestBuilder.removeHeader("Transfer-Encoding");
        } else {
            requestBuilder.header("Transfer-Encoding", "chunked");
            requestBuilder.removeHeader("Content-Length");
        }
    }
    Request networkRequest = requestBuilder.build();
    // 调用 chain.proceed 方法将请求传递给下一个拦截器
    Response networkResponse = chain.proceed(networkRequest);
    // 处理响应
    Response.Builder responseBuilder = networkResponse.newBuilder()
           .request(userRequest);
    return responseBuilder.build();
}

在 intercept 方法中调用 chain.proceed 方法,就会触发下一个拦截器的执行,进而保证拦截器链按顺序执行。

———————————————————————————————————————————

OkHttp 缓存机制详解(结合 Android 面试高频考点)

一、缓存核心组件与配置

Cache 类

作用:OkHttp 的缓存通过 Cache 类实现,基于磁盘存储(默认无内存缓存,需手动实现)。

初始化

File cacheDir = new File(context.getCacheDir(), "okhttp_cache");
OkHttpClient client = new OkHttpClient.Builder()
    .cache(new Cache(cacheDir, 10 * 1024 * 1024)) // 10MB 缓存大小
    .build();

面试点:缓存目录通常放在应用私有目录(如 getCacheDir()),避免权限问题;缓存大小需根据业务场景合理设置,过大浪费存储,过小导致缓存命中率低。

CacheInterceptor 拦截器

二、缓存策略(面试高频考点)

OkHttp 支持 HTTP 标准缓存策略(基于 Cache-Control 头)和 自定义策略,通过 Request 的 CacheControl 对象配置,常见策略:

强制缓存(不与服务器交互)

缓存优先(无有效缓存时请求网络)

网络优先(忽略缓存,仅存储响应)

协商缓存(与服务器验证缓存有效性)

三、缓存存储结构与 HTTP 头解析

缓存存储格式

OkHttp 将响应以 二进制文件 存储在磁盘,文件名由 URL 的哈希值生成,包含两部分:

关键 HTTP 头字段

Cache-Control

四、缓存流程与拦截器逻辑

网络请求与缓存写入(CacheInterceptor 后半段)

五、内存缓存与磁盘缓存(面试易混点)

六、缓存失效与更新

手动清除缓存

client.cache().delete(); // 清除所有缓存
client.cache().evictAll(); // 同上(API 差异)

策略强制更新

发起请求时添加 CacheControl.noCache(),强制忽略缓存,走网络请求。

七、面试高频问题总结

“OkHttp 缓存策略有哪些?如何实现缓存优先?”

答:支持 FORCE_CACHE(强制读缓存)、FORCE_NETWORK(强制网络)、协商缓存(304)等;缓存优先可通过 maxStale 允许过期缓存,配合网络请求更新。

“304 状态码在 OkHttp 缓存中如何处理?”

答:OkHttp 自动携带 ETag 生成 If-None-Match 头,服务器返回 304 时,复用本地缓存响应体,仅更新头信息(减少流量)。

“OkHttp 缓存和浏览器缓存的区别?”

答:核心逻辑一致(基于 HTTP 头),但 OkHttp 需手动配置 Cache 实例,且默认无内存缓存;浏览器缓存由浏览器自动管理。

“缓存拦截器的作用是什么?在拦截器链中的位置?”

答:负责缓存的读取和写入,位于拦截器链的中间位置(处理完重试、桥接,未处理连接和网络请求)。 总结

OkHttp 缓存机制通过 拦截器链 和 HTTP 标准头 实现高效的网络请求优化,核心在于合理配置 CacheControl 策略、利用协商缓存减少服务器压力,并结合磁盘 / 内存缓存提升性能。--

_____________________________________________________________________________

     OkHttp 的连接池复用是优化网络请求性能的重要手段,其核心是通过ConnectionPool管理底层 TCP 连接,避免重复建立连接的开销。 

一、OkHttp 连接池复用的核心原理

目标
复用相同 URL、相同协议(HTTP/HTTPS)的连接,减少 TCP 三次握手、TLS 握手的耗时,提升请求速度。

核心类:ConnectionPool

// OkHttpClient源码中的默认连接池
private static final ConnectionPool DEFAULT_CONNECTION_POOL = new ConnectionPool(
    5, // 最大空闲连接数(默认5个)
    5, TimeUnit.MINUTES // 空闲连接存活时间(默认5分钟)
);

关键参数

连接复用条件

二、如何使用连接池复用?

1. 默认使用(无需额外配置)

OkHttpClient 默认启用连接池,无需手动设置,同一OkHttpClient实例的所有请求共享同一个连接池:

OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .build(); // 内部使用默认的ConnectionPool

2. 自定义连接池配置(可选)

若需调整默认参数(如增大空闲连接数或存活时间),可通过connectionPool()方法设置:

OkHttpClient client = new OkHttpClient.Builder()
    .connectionPool(new ConnectionPool(
        10, // 最大空闲连接数设为10
        10, TimeUnit.MINUTES // 空闲连接存活时间设为10分钟
    ))
    .build();

3. 连接池的生命周期

client.connectionPool().evictAll(); // 清除所有连接

三、源码级实现细节(面试常问)

连接获取流程

当发起请求时,OkHttp 先从ConnectionPool中查找可用的空闲连接:

// RealConnectionPool.java 查找连接的核心逻辑
RealConnection get(Address address, StreamAllocation streamAllocation) {
    // 遍历连接池中的连接,寻找匹配address且空闲的连接
    for (RealConnection connection : connections) {
        if (connection.isEligible(address, streamAllocation)) {
            streamAllocation.acquire(connection);
            return connection;
        }
    }
    return null; // 无可用连接,新建连接
}

isEligible方法判断连接是否符合复用条件(host、port、协议一致,且未达最大请求数)。

连接释放与空闲标记

请求完成后,连接不会立即关闭,而是标记为 “空闲” 并放回连接池:

// RealConnection.java 释放连接的逻辑
void release(StreamAllocation streamAllocation) {
    if (streamAllocation == null) return;
    streamAllocation.release();
    if (allocationCount > 0 || noNewStreams) {
        return; // 连接仍在使用中
    }
    // 连接变为空闲,加入连接池的空闲队列
    connectionPool.put(this); 
}

清理机制

ConnectionPool通过CleanupRunnable线程定时执行cleanup()方法,移除超时或超出最大空闲数的连接:

// ConnectionPool.java 清理逻辑
private final Runnable cleanupRunnable = () -> {
    while (true) {
        long waitNanos = cleanup(System.nanoTime()); // 执行清理,返回下次等待时间
        if (waitNanos == -1) return; // 无需要清理的连接,退出
        if (waitNanos > 0) {
            synchronized (this) {
                try {
                    wait(waitNanos / 1000000, (int) (waitNanos % 1000000));
                } catch (InterruptedException e) {
                    return;
                }
            }
        }
    }
};

四、面试高频问题与解答

1. 为什么需要连接池复用?相比 HTTPURLConnection 有什么优势?

原因:避免重复建立 TCP 连接(三次握手)和 TLS 握手(HTTPS 场景),减少延迟和资源消耗。

优势

2. 如何判断两个请求是否可以复用同一个连接?

必须满足:

3. 连接池中的连接会一直存在吗?如何避免内存泄漏?

不会

最佳实践:使用单例OkHttpClient(避免创建多个实例导致多个连接池),并合理设置maxIdleConnections(通常默认值即可)。

4. 连接池和缓存机制(CacheInterceptor)的关系是什么?

五、最佳实践

单例模式:全局共享一个OkHttpClient实例,避免重复创建连接池:

public class OkHttpSingleton {
    private static OkHttpClient client;
    public static OkHttpClient getInstance() {
        if (client == null) {
            synchronized (OkHttpSingleton.class) {
                if (client == null) {
                    client = new OkHttpClient.Builder()
                        .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES))
                        .build();
                }
            }
        }
        return client;
    }
}

总结

OkHttp 的连接池复用通过ConnectionPool自动管理空闲连接,显著提升网络请求效率。使用时无需手动干预,只需合理配置参数(或使用默认值),并遵循单例模式共享OkHttpClient实例即可。

扩展追问:

1. OkHttp 如何实现连接池复用?(高频题)

核心回答点:

2. OkHttp 缓存机制的核心策略是什么?如何配置?(必问题)

核心回答点:

两层缓存

3. 拦截器链(Interceptor Chain)的作用是什么?自定义拦截器如何实现?(原理题)

核心回答点:

4. 同步请求(execute)和异步请求(enqueue)的区别是什么?如何实现线程切换?(线程题)

核心回答点:

5. OkHttp 相比 Volley 或 HttpURLConnection 的优势是什么?(对比题)

核心回答点:

6. 如何优化 OkHttp 的网络请求性能?(实战题)

核心回答点:

到此这篇关于Android学习总结之OKHttp拦截器和缓存的文章就介绍到这了,更多相关Android OKHttp拦截器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文