OkHttp拦截器是一种强大的机制,可以监视、重写和重试调用
1、LogInterceptor 示例代码
class LogInterceptor:Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val mRequest=chain.request()
// startTime
val startTime=System.currentTimeMillis()
LogUtil.D(log = "Sending request url== ${mRequest.url} " +
" connection== ${chain.connection()} " +
" headers==${mRequest.headers}")
val mResponse=chain.proceed(mRequest)
val endTime=System.currentTimeMillis()
LogUtil.D(log="Received response url== ${mResponse.request.url} " +
" costTime== ${endTime-startTime}ms"+
" header== ${mResponse.headers}")
return mResponse
}
}
2、拦截器使用注意事项
调用chain.process(request)是每个拦截器实现的关键部分,该方法是所有HTTP请求产生响应的地方。如果chain.proceed(request)被多次调用,则必须关闭先前的响应体,否则会发生崩溃。
多个拦截器可以连接起来使用,假设有一个压缩拦截器和一个校验拦截器:你需要决定是先压缩数据然后校验,还是校验和然后压缩。OkHttp 使用列表来跟踪拦截器,拦截器按顺序调用。
3、OkHttp拦截器交互图
4、应用拦截器
- 1、 添加应用拦截器
OkHttpClient.Builder().addInterceptor(LogInterceptor()).build()
- 2 、请求www.baidu.com 查看应用拦截器日志
// request
D/tag: Sending request url== https://www.baidu.com/ connection== null headers==
// response
D/tag: Received response url== https://www.baidu.com/ costTime== 145ms header== Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Type: text/html
Date: Thu, 08 Jul 2021 07:38:20 GMT
Last-Modified: Mon, 23 Jan 2017 13:23:46 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
Transfer-Encoding: chunked
- 3、应用拦截器只打印了一遍日志
6、使用网络拦截器 NetworkInterceptor
注册网络拦截器非常相似。调用addNetworkInterceptor()而不是addInterceptor()
- 1、添加网络拦截器
OkHttpClient.Builder().addNetworkInterceptor(LogInterceptor()).build()
- 2、请求https://www.publicobject.com/helloworld.txt,网络拦截器日志
D/tag: Sending request url== https://www.publicobject.com/helloworld.txt connection== Connection{www.publicobject.com:443, proxy=DIRECT hostAddress=www.publicobject.com/54.187.32.157:443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 protocol=http/1.1} headers==User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
D/tag: Received response url== https://www.publicobject.com/helloworld.txt costTime== 214ms header== Server: nginx/1.10.3 (Ubuntu)
Date: Thu, 08 Jul 2021 09:30:19 GMT
Content-Type: text/html
Content-Length: 194
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt
D/tag: Sending request url== https://publicobject.com/helloworld.txt connection== Connection{publicobject.com:443, proxy=DIRECT hostAddress=publicobject.com/54.187.32.157:443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 protocol=http/1.1} headers==User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
D/tag: Received response url== https://publicobject.com/helloworld.txt costTime== 493ms header== Server: nginx/1.10.3 (Ubuntu)
Date: Thu, 08 Jul 2021 09:30:20 GMT
Content-Type: text/plain
Content-Length: 1759
Last-Modified: Tue, 27 May 2014 02:35:47 GMT
Connection: keep-alive
ETag: "5383fa03-6df"
Accept-Ranges: bytes
2021-07-08 17:30:23.855 19772-19847/com.example.myapplication D/tag: get success response=== okhttp3.internal.http.RealResponseBody@93be2de
- 3、网络拦截器打印了两次日志,一次用于初始化请求,一次用于进行重定向请求。相较于普通拦截器网络拦截器包含更多的信息,包括Hosts、 Accept-Encoding等
7、应用拦截器与网络拦截器特点
1) 应用拦截器
- 无需担心重定向和重试等中间响应。
- 始终调用一次,即使 HTTP 响应是从缓存中提供的。
- 遵循应用程序的原始意图。不关心 OkHttp 注入的标头,如If-None-Match.
- 缓存命中时将拦截器短路,不调用Chain.proceed()。
- 允许重试并多次调用Chain.proceed()。
- 可以使用 withConnectTimeout、withReadTimeout、withWriteTimeout控制请求超时时长
2) 网络拦截器
- 能够对重定向和重试等中间响应进行操作。
- 不会因为使用缓存响应而不发生调用。
- 可以像通过网络传输一样观察数据。
- 以携带请求的方式访问Connection
8、重写请求
- 应用拦截器可以用来添加、移除、替换请求头headers,能够用来转换requests中的请求体,下面为使用应用拦截器进行gzip压缩例子:
- GzipRequestInterceptor
class GzipRequestInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originRequest = chain.request()
if (originRequest == null || originRequest.header("Content-Encoding") != null) {
return chain.proceed(originRequest)
}
// compressedRequest
val compressedRequest = originRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originRequest.method, originRequest.body?.let { CustomRequestBody(it) })
.build()
return chain.proceed(compressedRequest)
}
}
class CustomRequestBody(body: RequestBody) : RequestBody() {
private val mBody = body
override fun contentType(): MediaType? {
return mBody.contentType()
}
override fun writeTo(sink: BufferedSink) {
val gzipSink = GzipSink(sink).buffer()
mBody.writeTo(gzipSink)
gzipSink.close()
}
override fun contentLength(): Long {
return -1
}
}
9、重写响应
- 通常情况下,OkHttp会帮我们处理响应头并返回响应体,但是一些业务中可能不满足需求,可以对响应头进行重写,满足需求。
- 代码示例
class CacheAgeInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val mOriginRequest = chain.request()
val mOriginResponse = chain.proceed(mOriginRequest)
// setMaxCacheAge
return mOriginResponse.newBuilder().header("Cache-Control", "max-age=60").build()
}
}
- 参考:https://square.github.io/okhttp/interceptors/