目的
关于 OkHttp 的拦截器的文章有很多,这篇文章旨在介绍:
- 拦截器的基本原理
- 如何自定义一个拦截器
- Application Interceptors 和 Network Interceptors 的不同
自定义拦截器的写法
官方文档 中介绍了如何自定义一个 Interceptor,代码如下:
class HeaderInterceptor() : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
...
val response = chain.proceed(request)
...
return response
}
}
可以看到主要分为 request
和 response
两部分,并且 reponse
必须要要调用 chain.proceed(request)
。
然后使用下面的两个方法将拦截器添加到 OkHttp 中:
OkHttpClient.Builder()
// 添加网络拦截器
.addNetworkInterceptor(...)
// 添加应用拦截器
.addInterceptor(...)
使用方法非常的简单,至于为什么要这么写,我们不妨提出下面几个问题,解答了这几个问题就可以掌握自定义拦截器的相关知识了:
Chain
是什么,为什么还要调用chain.proceed(request)
?Request
中都有什么信息,如何重写Request
?Response
中都有什么信息,如何重写Response
?
Chain
chain 就是 链条
的意思,OkHttp 的拦截器采用了责任链设计模式,那么这个责任链是如何构造的呢?
责任链模式简介
我们先来了解一下什么是责任链模式。 要实现一个责任链需要先定义一个接口,比如我们设计一个上传功能责任链,会依次上传String,Image,File,接口实现如下:
interface Uploader {
fun doUpload(data: Data, chain: UploadChain)
}
然后依次定义 String,Image,File 的上传器:
class StringUploader : Uploader {
override fun doUpload(data: Data, chain: UploadChain) {
...
chain.doUpload(data, chain)
return
}
}
class ImageUploader : Uploader {
override fun doUpload(data: Data, chain: UploadChain) {
...
chain.doUpload(data, chain)
return
}
}
class FileUploader : Uploader {
override fun doUpload(data: Data, chain: UploadChain) {
...
chain.doUpload(data, chain)
return
}
}
我们发现有一个 UploadChain 类,为什么要定义它呢?StringUploader、ImageUploader、FileUploader 可以想象成是责任链的节点,我们需要一个责任链的管理者来添加/依次调用这些节点:
class UploadChain() : Uploader {
// 记录节点的角标
private var position = 0
private val uploaderList = mutableListOf<Uploader>()
override fun doUpload(data: Data, chain: UploadChain) {
if (position == uploaderList.size) {
...
// 责任链执行完成
return
}
// 执行下一个节点
uploaderList[position++].doUpload(data, chain)
}
// 添加节点
fun addUploader(uploader: Uploader) {
uploaderList.add(uploader)
}
}
使用的时候我们先创建一个链条,然后将 String,Image,File 节点依次添加进入,然后调用第一个节点:
val uploadChain = UploadChain()
uploadChain.addUploader(StringUploader())
uploadChain.addUploader(ImageUploader())
uploadChain.addUploader(FileUploader())
uploadChain.doUpload(data,uploadChain)
OkHttp 构造责任链
接口定义
OkHttp 拦截器接口定义如下:
interface Interceptor {
@Throws(IOException::class)
fun intercept(chain: Chain): Response
interface Chain {
fun request(): Request
@Throws(IOException::class)
fun proceed(request: Request): Response
fun connection(): Connection?
fun call(): Call
...
}
}
Chain 的实现类是 RealInterceptorChain,它的源码结构和上面的 UploadChain 是类似的,有 Interceptor 的集合,当前节点的角标 index,以及触发方法 proceed。
RealInterceptorChain
class RealInterceptorChain(
private val interceptors: List<Interceptor>,
private val index: Int,
) : Interceptor.Chain {
override fun proceed(request: Request): Response {
return proceed(request, transmitter, exchange)
}
@Throws(IOException::class)
fun proceed(request: Request, transmitter: Transmitter, exchange: Exchange?): Response {
if (index >= interceptors.size) throw AssertionError()
calls++
...
// Call the next interceptor in the chain.
val next = RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout)
val interceptor = interceptors[index]
val response = interceptor.intercept(next)
...
return response
}
}
chain.proceed 的作用
- 将当前事件交给责任链的下一个拦截器处理。
递归返回 response
RealInterceptorChain 和上面 UploadChain 有一些不同的地方,UploadChain 只是顺序执行了责任链的每个节点,而 RealInterceptorChain 每个节点不止要处理 Request 还需要返回 Response,所以下一个拦截器会通过递归将 Response 返回给上一个拦截器。
添加拦截器的顺序
我们再来看一下是如何将 Interceptor 添加到 Chain 中的,在 RealCall 每次实例化一个 Chain 的时候都会添加一系列的拦截器:
fun getResponseWithInterceptorChain(): Response {
val interceptors = mutableListOf<Interceptor>()
// 添加自定义的应用拦截器
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
// 添加自定义的网络拦截器
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
// 实例化一个 Chain
val chain = RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this,
client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)
// 开始触发第一个拦截器
val response = chain.proceed(originalRequest)
...
return response
}
}
OkHttp 默认实现的拦截器这里不再讲述,可以参考这篇文章 okhttp3 拦截器源码分析 这里我们只关心拦截器的添加顺序。
Request
Request 包含了请求头(header),请求体(body),访问的的 url。
对 Request 比较常规的操作:
- 添加公共 header:
class HeaderInterceptor(private val headers: Map<String, String>?) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
if (!headers.isNullOrEmpty()) {
val builder = request.newBuilder()
headers.forEach {
builder.header(it.key, it.value)
}
request = builder.build()
}
val response = chain.proceed(request)
return response
}
}
- 打印请求日志,可以直接参考官方提供的 HttpLoggingInterceptor
- Gzip 加密
- 抓包:参考 steho
- DNS 相关
- 修改 url 进行重定向
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
request.newBuilder().url(customUrl).build()
...
}
此外,比较有趣的是 request 还有一个 tag 字段,如果你使用 retrofit 的话,它有一个实用的用法,有兴趣的可以看一下 使用拦截器扩展 retrofit 的注解类型。
Response
Response 中的信息和 Request 比较类似,包含了服务器或者本地返回响应头,响应体。 对 Resposne 的操作一般有:
- Gzip 解压
- 加载本地缓存:不调用 chain.proceed, 返回自己的 response 短路拦截器
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
// 获取本地数据
val mockData = getMockData(mock)
// 重写一个 Response 进行短路操作
if (!mockData.isNullOrEmpty()) {
return Response.Builder()
.protocol(Protocol.HTTP_1_0)
.code(200)
.request(request)
.message("ok")
.body(mockData.toResponseBody(null))
.build()
}
return chain.proceed(request)
}
Application Interceptors 和 Network Interceptors 的不同
执行顺序
借用上面文章的一张图,发现 OkHttp 会先执行应用拦截器,再执行网络拦截器:

调用次数
应用拦截器只会调用一次,网络拦截器可能调用多次。
其实很好理解,网络拦截器是在重试重定向拦截器之后执行的,如果重试了又会重新调用一次,所以应用拦截器不受重试的影响,网络拦截器受重试的影响。
所以像添加 header,加密,Gzip 压缩 这种修改 request 的操作就可以使用应用拦截器,而像打印 log 的这种操作就需要使用网络拦截器。
短路操作
应用拦截器允许不调用 Chain.proceed()
进行短路操作,所以像读取缓存的这种拦截器就可以使用应用拦截器。
Connection 参数
Chain 有一个 connect 方法,只有网络拦截器才会返回值,应用拦截器会返回 null:
interface Chain {
...
/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
fun connection(): Connection?
}
Connection 包含了本次网络请求比较底层的 socket,握手操作等,目前只看到抓包工具 Steho 在使用。