前言
想象一下,你是一个快递站的老板。每天有成千上万的包裹(网络请求)从这里发出,也有无数的包裹(响应)送回来。你需要检查每个包裹是否贴了正确的标签(请求头),记录每个包裹的物流信息(日志),甚至拦截某些包裹进行特殊处理(比如加密或解密)。这时候,你需要的不是一个一个打开包裹检查,而是在包裹的传送带上安装一系列的「关卡」,每个关卡负责特定的任务。
OkHttp的拦截器(Interceptor)就是这样的「关卡」。它允许你在网络请求的生命周期中插入自定义逻辑,比如添加公共请求头、记录请求日志、处理缓存、自动刷新Token等。通过拦截器,你可以在不修改业务代码的情况下,统一处理所有网络请求的通用逻辑,让代码更简洁、可维护性更强。
拦截器的基本概念:应用拦截器 vs 网络拦截器
OkHttp的拦截器分为两种类型:应用拦截器 和网络拦截器。它们的区别就像快递站的「前台」和「仓库」:
1. 应用拦截器(Application Interceptor)
- 位置:在快递站的前台,负责处理用户直接提交的包裹。
- 特点 :
- 不关心网络:不管包裹是从网络发送还是从缓存读取,应用拦截器都会被调用。
- 不处理重定向和重试:如果包裹需要重定向(比如302响应)或重试,应用拦截器不会感知到这些过程。
- 只调用一次:每个请求只会调用一次应用拦截器,即使请求被重试多次。
- 使用场景:添加公共请求头、记录全局日志、处理响应数据(如解密)等。
2. 网络拦截器(Network Interceptor)
- 位置:在快递站的仓库,负责处理实际发送到网络的包裹。
- 特点 :
- 关心网络:只有当包裹真正通过网络发送时,网络拦截器才会被调用。如果包裹来自缓存,网络拦截器不会执行。
- 处理重定向和重试:网络拦截器会感知到请求的重定向和重试过程,并且会在每次网络请求时被调用。
- 可访问原始数据:可以获取到请求和响应的原始字节流,适合处理网络层的细节,比如监控流量、修改原始数据等。
- 使用场景:监控网络请求的实际传输过程、处理网络层的重试逻辑、修改原始请求体等。
自定义拦截器的实现:从Hello World开始
现在,我们来实现一个简单的日志拦截器,记录每个请求的URL、请求头和响应状态码。
1. 创建拦截器类
kotlin
class LoggingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val url = request.url.toString()
val method = request.method
val headers = request.headers.toMultimap()
// 打印请求信息
println("🌏 Request: $method $url")
println("📦 Headers: $headers")
// 执行请求
val response = chain.proceed(request)
// 打印响应信息
val statusCode = response.code
println("🚀 Response: $statusCode from $url")
return response
}
}
2. 将拦截器添加到OkHttpClient
kotlin
val client = OkHttpClient.Builder()
.addInterceptor(LoggingInterceptor()) // 添加应用拦截器
.build()
3. 代码解释
- chain.proceed(request):这是拦截器链的核心方法。它会将当前请求传递给下一个拦截器,并返回最终的响应。如果不调用这个方法,请求链就会中断,不会发送到服务器。
- 请求处理 :在调用
chain.proceed(request)
之前,我们可以修改请求(如添加请求头),或者直接返回一个伪造的响应。 - 响应处理 :在调用
chain.proceed(request)
之后,我们可以处理响应(如解密数据),或者修改响应后返回。
应用拦截器 vs 网络拦截器:详细对比
为了让你更清楚地理解两者的区别,我们用一个表格来总结:
特性 | 应用拦截器 | 网络拦截器 |
---|---|---|
调用时机 | 所有请求(包括缓存)都会调用 | 只有实际发送到网络的请求才会调用 |
重定向/重试 | 不感知,只处理原始请求 | 感知,可以处理重定向和重试后的请求 |
调用次数 | 每个请求调用一次 | 每次网络请求调用一次(可能多次) |
原始数据访问 | 无法获取原始字节流 | 可以获取请求和响应的原始字节流 |
最佳实践 | 处理全局逻辑(如添加公共头、日志) | 处理网络层细节(如监控流量、修改原始数据) |
举个🌰:假设你需要记录所有请求的日志,不管是来自网络还是缓存,那么应用拦截器是更好的选择。如果你需要监控每个网络请求的实际传输时间,那么网络拦截器更合适。
实际应用场景:拦截器的「十八般武艺」
拦截器的应用场景非常广泛,下面我们列举几个常见的例子:
1. 日志记录:监控网络请求的「天眼」
日志拦截器是最常见的拦截器之一。它可以记录请求的URL、方法、请求头、响应状态码等信息,帮助你快速定位网络问题。
kotlin
class LoggingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val url = request.url.toString()
val method = request.method
val headers = request.headers.toMultimap()
// 打印请求信息
println("🌏 Request: $method $url")
println("📦 Headers: $headers")
// 执行请求
val response = chain.proceed(request)
// 打印响应信息
val statusCode = response.code
println("🚀 Response: $statusCode from $url")
return response
}
}
2. 缓存处理:让请求「飞」得更快
通过缓存拦截器,你可以将常用的响应缓存到本地,减少网络请求的次数,提升应用性能。
kotlin
class CacheInterceptor(private val cacheSize: Long) : Interceptor {
private val cache = Cache(File(context.cacheDir, "okhttp_cache"), cacheSize)
override fun intercept(chain: Interceptor.Chain): Response {
// 检查缓存
val cachedResponse = cache.get(chain.request())
if (cachedResponse != null) {
println("📤 Using cached response")
return cachedResponse
}
// 发起网络请求
val response = chain.proceed(chain.request())
// 缓存响应
cache.put(response.newBuilder().build())
println("📥 Cached response")
return response
}
}
3. Token自动刷新:避免用户频繁登录
当Token过期时,拦截器可以自动刷新Token,并重新发起请求,让用户无感知。
kotlin
class TokenRefreshInterceptor(private val tokenManager: TokenManager) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
var response = chain.proceed(request)
// 如果Token过期,刷新Token并重新请求
if (response.code == 401) {
tokenManager.refreshToken()
request = request.newBuilder()
.header("Authorization", "Bearer ${tokenManager.getToken()}")
.build()
response = chain.proceed(request)
}
return response
}
}
4. 参数加密:让数据传输更安全
拦截器可以对请求参数进行加密,防止数据在传输过程中被窃取。
kotlin
class EncryptionInterceptor(private val encryptor: Encryptor) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val body = request.body
// 加密请求体
if (body != null) {
val encryptedBody = encryptor.encrypt(body.toString())
val newBody = RequestBody.create(MediaType.parse("application/json"), encryptedBody)
request = request.newBuilder()
.post(newBody)
.build()
}
return chain.proceed(request)
}
}
高级技巧:拦截器的「进阶玩法」
1. 动态域名切换:给快递换地址标签(含HTTPS证书兼容)
在多环境部署(如开发、测试、生产)或灰度发布场景中,我们需要动态切换服务器域名。这时,拦截器就像一个「地址标签更换器」,在请求发出前自动替换域名。不过,HTTPS证书的兼容性问题可能会让你头疼------这就好比给快递换地址时,还得确保新地址的门锁能匹配你的钥匙。
实战案例:多环境动态切换
kotlin
class DynamicDomainInterceptor(private val domainManager: DynamicDomainManager) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val newUrl = originalRequest.url.newBuilder()
.host(domainManager.currentDomain) // 替换域名
.build()
val newRequest = originalRequest.newBuilder()
.url(newUrl)
.build()
return chain.proceed(newRequest)
}
}
// 动态域名管理类(线程安全)
object DynamicDomainManager {
private var currentDomain: String = "https://prod.api.com" // 默认生产环境
private val lock = Any()
fun updateDomain(newDomain: String) {
synchronized(lock) {
currentDomain = newDomain
// 可选:持久化到SharedPreferences或数据库
}
}
val currentDomain: String
get() = synchronized(lock) { currentDomain }
}
HTTPS证书兼容方案
如果不同域名使用不同证书,你需要配置OkHttp的SSLContext
。例如,为测试环境添加自签名证书:
kotlin
// 加载证书(放在assets目录)
val certificate = context.assets.open("test_cert.crt").use { inputStream ->
CertificateFactory.getInstance("X.509").generateCertificate(inputStream) as X509Certificate
}
// 创建自定义TrustManager
val trustManager = object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
chain.forEach { it.checkValidity() } // 验证证书有效期
}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf(certificate)
}
// 配置OkHttpClient
val client = OkHttpClient.Builder()
.sslSocketFactory(SSLContext.getInstance("TLS").apply {
init(null, arrayOf(trustManager), null)
}.socketFactory, trustManager)
.addInterceptor(DynamicDomainInterceptor(DynamicDomainManager))
.build()
实现原理
- 动态域名管理类:通过单例和锁机制保证线程安全,支持实时更新域名。
- 拦截器替换逻辑 :在
intercept
方法中,使用newBuilder()
修改请求的host
字段。 - HTTPS证书兼容 :通过自定义
TrustManager
信任特定证书,避免SSLHandshakeException
。
2. 重试机制:给网络请求上「保险绳」(协程版)
网络波动时,自动重试可以大幅提升请求成功率。拦截器能像「保险绳」一样,在请求失败时自动重试。这里我们用协程实现非阻塞的指数退避重试。
带指数退避的协程重试拦截器
kotlin
class CoroutineRetryInterceptor(
private val maxRetries: Int = 3,
private val baseDelayMs: Long = 1000
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
var retries = 0
while (retries < maxRetries) {
try {
return chain.proceed(request)
} catch (e: IOException) {
retries++
if (retries > maxRetries) throw e
// 协程中使用delay,避免阻塞线程
withContext(Dispatchers.IO) {
delay(baseDelayMs * (2.0.pow(retries - 1).toLong()))
}
}
}
throw IOException("Max retries exceeded")
}
}
核心策略
-
指数退避:重试间隔随失败次数翻倍(如1s → 2s → 4s),避免服务器被频繁重试压垮。
-
协程优势 :使用
Dispatchers.IO
在后台线程等待,不影响主线程。 -
状态码过滤 :可扩展为仅对特定状态码(如500、503)重试:
kotlinif (response.code !in 200..299) { retries++ }
3. 性能监控:给网络请求做「体检」(含数据可视化)
拦截器能像「网络医生」一样,记录请求耗时、流量、状态码等关键指标,帮助优化性能。数据记录后,你可以用开源库(如OkLog)生成可视化报告。
性能监控拦截器
kotlin
class PerformanceInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val startNs = System.nanoTime()
val request = chain.request()
val response = chain.proceed(request)
val durationMs = (System.nanoTime() - startNs) / 1_000_000
// 记录关键指标
val metrics = mapOf(
"url" to request.url.toString(),
"method" to request.method,
"duration_ms" to durationMs,
"status_code" to response.code,
"size_bytes" to response.body?.contentLength() ?: 0
)
// 使用OkLog发送数据到Chrome开发者工具
OkLog.log(metrics)
return response
}
}
数据应用
- 请求耗时分析:找出慢接口,优化后端或增加缓存。
- 流量监控:防止大文件传输导致用户流量超限。
- 可视化工具 :
- OkLog:在日志中生成可点击的链接,直接查看响应详情。
- Monitor:在应用内显示实时网络数据面板。
4. 拦截器链控制:掌控请求「流水线」(含Stetho集成)
通过拦截器可以灵活控制请求链的执行,甚至提前终止流程。这在调试时尤其有用,比如模拟响应。
模拟响应拦截器(调试神器)
kotlin
class MockInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
if (request.url.pathSegments.contains("mock")) {
// 返回模拟数据,跳过网络请求
return Response.Builder()
.request(request)
.code(200)
.body(ResponseBody.create(MediaType.parse("application/json"), """
{
"mock": "data",
"message": "This is a mock response"
}
""".trimIndent()))
.build()
}
return chain.proceed(request)
}
}
调试技巧
-
与Stetho集成 :在Chrome开发者工具中查看模拟响应:
kotlinOkHttpClient.Builder() .addNetworkInterceptor(StethoInterceptor()) // 抓包工具 .addInterceptor(MockInterceptor()) .build()
-
动态数据:从本地文件或数据库读取模拟数据,支持不同场景。
5. 数据解密:给敏感数据「剥壳」(AES加密实战)
如果服务器返回的数据是加密的(如AES加密),可以用拦截器自动解密,避免在每个接口回调中重复处理。
AES解密拦截器
kotlin
class DecryptInterceptor(private val decryptor: Decryptor) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
val encryptedBody = response.body?.string() ?: return response
// 解密响应体
val decryptedBody = decryptor.aesDecrypt(encryptedBody)
// 构建新的响应体
val newBody = ResponseBody.create(
response.body?.contentType(),
decryptedBody
)
return response.newBuilder()
.body(newBody)
.build()
}
}
// 解密接口
interface Decryptor {
fun aesDecrypt(encryptedData: String): String
}
// 实现示例
class AesDecryptor(private val key: String) : Decryptor {
override fun aesDecrypt(encryptedData: String): String {
// 使用AES算法解密
val cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key.toByteArray(), "AES"))
return String(cipher.doFinal(Base64.getDecoder().decode(encryptedData)))
}
}
安全注意事项
- 密钥管理:避免硬编码密钥,可通过服务端动态下发。
- 加密算法:优先使用更安全的模式(如AES/GCM)。
- 性能优化:解密操作可能耗时,建议在网络拦截器中使用(不处理缓存)。
6. 流量控制:给请求「限速」(信号量实现)
在弱网环境下,过多的并发请求会导致网络拥堵。拦截器可以实现简单的流量控制,限制同时发起的请求数量。
流量控制拦截器
kotlin
class TrafficControlInterceptor(
private val maxConcurrentRequests: Int = 3 // 最大并发请求数
) : Interceptor {
private val semaphore = Semaphore(maxConcurrentRequests) // 信号量控制并发
override fun intercept(chain: Interceptor.Chain): Response {
semaphore.acquire() // 获取许可,若已满则阻塞等待
return try {
chain.proceed(chain.request())
} finally {
semaphore.release() // 释放许可
}
}
}
适用场景
-
列表页快速滑动:限制同时加载的图片或数据请求。
-
启动优化:避免App启动时大量初始化请求瞬间涌入网络。
-
与Dispatcher结合 :
kotlinOkHttpClient.Builder() .dispatcher(Dispatcher().apply { maxRequests = 5 // 全局并发限制 }) .addInterceptor(TrafficControlInterceptor(3)) // 接口级并发限制 .build()
7. 线程安全设计:给拦截器上「安全锁」(Token管理优化)
多线程环境下,拦截器可能被并发调用,需确保线程安全。例如,Token刷新逻辑需要原子操作。
线程安全的Token管理器
kotlin
class TokenManager {
private val currentToken = AtomicReference<String>("")
suspend fun fetchToken(): String {
return withContext(Dispatchers.IO) {
// 异步请求刷新Token
val response = apiService.refreshToken()
currentToken.set(response.token)
response.token
}
}
val token: String
get() = currentToken.get()
}
安全策略
-
原子操作 :使用
AtomicReference
保证Token的线程安全访问。 -
协程同步 :在协程中使用
synchronized
块:kotlinprivate val lock = Any() suspend fun refreshToken() { synchronized(lock) { // 刷新Token的逻辑 } }
8. 动态请求头注入:按需贴「特殊标签」
根据不同请求动态添加请求头,例如用户权限或设备信息。
动态请求头拦截器
kotlin
class DynamicHeaderInterceptor(private val headerProvider: HeaderProvider) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val headers = headerProvider.getHeaders(request.url)
val newRequest = request.newBuilder()
.headers(headers)
.build()
return chain.proceed(newRequest)
}
}
// 头信息提供器示例
class PathBasedHeaderProvider : HeaderProvider {
override fun getHeaders(url: HttpUrl): Headers {
return if (url.pathSegments.contains("admin")) {
Headers.of("X-Permission", "admin")
} else {
Headers.of("X-Permission", "user")
}
}
}
应用场景
- 权限控制:根据用户角色动态添加权限头。
- A/B测试:为不同用户群体注入差异化头信息。
- 设备标识:自动添加设备型号、系统版本等头。
拦截器链的「黑科技」
1. 拦截器顺序的「蝴蝶效应」
拦截器的添加顺序会影响执行逻辑。例如:
kotlin
OkHttpClient.Builder()
.addInterceptor(TokenRefreshInterceptor()) // 先刷新Token
.addInterceptor(RetryInterceptor()) // 再重试
.addInterceptor(PerformanceInterceptor()) // 最后记录性能
.build()
- TokenRefreshInterceptor在请求前添加Authorization头。
- RetryInterceptor在Token过期时重试请求。
- PerformanceInterceptor记录整个流程的耗时。
2. 拦截器链的「短路」
拦截器可以提前返回响应,终止后续拦截器的执行。例如:
kotlin
class AuthInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
if (request.headers["Authorization"] == null) {
// 返回401未授权响应,跳过后续拦截器
return Response.Builder()
.request(request)
.code(401)
.body(ResponseBody.create(null, "Unauthorized"))
.build()
}
return chain.proceed(request)
}
}
总结
OkHttp拦截器就像一个「瑞士军刀」,可以灵活应对各种网络场景:
- 动态域名切换:应对多环境部署,含HTTPS证书兼容。
- 重试机制:网络波动时自动重试,协程版避免阻塞。
- 性能监控:记录关键指标,结合可视化工具优化性能。
- 模拟响应:调试神器,快速验证接口逻辑。
- 数据解密:处理加密响应,保护敏感数据。
- 流量控制:限制并发请求,避免网络拥堵。
- 线程安全设计:确保多线程环境下的稳定性。
通过合理组合这些技巧,你可以构建出健壮、高效的网络层架构。拦截器的强大之处在于它的「无侵入性」------无需修改原有代码,就能为整个网络请求流程注入新功能。现在,拿起你的「瑞士军刀」,在Android开发中尽情发挥吧!