文章

一文搞懂如何自定义 OkHttp 拦截器

一文搞懂如何自定义 OkHttp 拦截器

本文转载自 一文搞懂如何自定义 OkHttp 拦截器(作者:TravelingLight_)。版权归原作者所有,此处仅作个人学习备份。

目的

关于 OkHttp 的拦截器的文章有很多,这篇文章旨在介绍:

  • 拦截器的基本原理
  • 如何自定义一个拦截器
  • Application Interceptors 和 Network Interceptors 的不同

自定义拦截器的写法

官方文档中介绍了如何自定义一个 Interceptor,代码如下:

1
2
3
4
5
6
7
8
9
class HeaderInterceptor() : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()
        ...
        val response = chain.proceed(request)
        ...
        return response
    }
}

可以看到主要分为 requestresponse 两部分,并且 reponse 必须要要调用 chain.proceed(request)

然后使用下面的两个方法将拦截器添加到 OkHttp 中:

1
2
3
4
5
 OkHttpClient.Builder()
// 添加网络拦截器 
.addNetworkInterceptor(...)
// 添加应用拦截器
.addInterceptor(...)

使用方法非常的简单,至于为什么要这么写,我们不妨提出下面几个问题,解答了这几个问题就可以掌握自定义拦截器的相关知识了:

  • Chain 是什么,为什么还要调用 chain.proceed(request)?
  • Request 中都有什么信息,如何重写 Request
  • Response 中都有什么信息,如何重写 Response

Chain

chain 就是 链条 的意思,OkHttp 的拦截器采用了责任链设计模式,那么这个责任链是如何构造的呢?

责任链模式简介

我们先来了解一下什么是责任链模式。要实现一个责任链需要先定义一个接口,比如我们设计一个上传功能责任链,会依次上传String,Image,File,接口实现如下:

1
2
3
interface Uploader {
    fun doUpload(data: Data, chain: UploadChain)
}

然后依次定义 String,Image,File 的上传器(StringUploader、ImageUploader、FileUploader,均实现 Uploader 接口,在 doUpload 中处理后调用 chain.doUpload 继续下一个)。它们可以想象成是责任链的节点,我们需要一个责任链的管理者来添加/依次调用这些节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 节点依次添加进入,然后调用第一个节点:

1
2
3
4
5
6
val uploadChain = UploadChain()
uploadChain.addUploader(StringUploader())
uploadChain.addUploader(ImageUploader())
uploadChain.addUploader(FileUploader())

uploadChain.doUpload(data,uploadChain)

OkHttp 构造责任链

OkHttp 拦截器接口定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 的时候都会添加一系列的拦截器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
    } 
  }

Request

Request 包含了请求头(header),请求体(body),访问的的 url。

对 Request 比较常规的操作:

  • 添加公共 header:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 加密
  • 抓包:参考 stetho
  • DNS 相关
  • 修改 url 进行重定向
1
2
3
4
5
  override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        request.newBuilder().url(customUrl).build()
        ...
        }

Response

Response 中的信息和 Request 比较类似,包含了服务器或者本地返回响应头,响应体。 对 Resposne 的操作一般有:

  • Gzip 解压
  • 加载本地缓存:不调用 chain.proceed, 返回自己的 response 短路拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 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。Connection 包含了本次网络请求比较底层的 socket,握手操作等。

本文由作者按照 CC BY 4.0 进行授权