本文翻译于Amit Shekhar的博客
1.简介
- 在这篇博客中,我们将学习如何使用OkHttp的拦截器。我们将能够看到关于我们在什么地方使用它和我们怎样充分利用它真实的运用实例
- 我们将通过以下几个部分来掌握它:
- 什么是拦截器?
- 拦截器的所有类型
- 怎样添加拦截器到OkHttpClient?
- 创建拦截器
- 使用拦截器的实例
2.什么是拦截器?
从文档中可以知道,拦截器是一个可以监测,重写,重试API调用的强大的机制。因此主要的就是,当我们在调用API的时候,我们能够监测这个调用或是执行某些任务。
- 简单来说,这些拦截器就像机场在安全检查通道的安检人员一样。他们检查我们的登机牌,在上面盖个印章然后允许我们通过。
- 我们能够使用拦截器做很多事情,比如,集中监测 API调用。通常,我们需要针对每个网络调用添加日志,但是使用拦截器的,我们能够集中添加一个日志就能够为所有的网络调用工作。另一个使用实例是能够构建一个离线优先的app,我们将在这篇博客的后面详细的介绍。
3. 拦截器的所有种类
- 我们有如下两种类型的拦截器:
- 应用层面的拦截器(Application Interceptor):这些拦截器被添加在应用程序代码(我们书写的代码)和OkHttp 的核心库之间的拦截器。这些是通过addInterceptor()的方式添加的。
- 网络层面的拦截器(Network Interceptors):这些拦截器被添加在核心类库与服务器之间。这些拦截器能够被添加到OkHttpClient 使用addNetworkInterceptor();
4.怎样添加拦截器到OkHttpClient?
- 当我们去构建OkHttpClient对象的时候,我们能够添加拦截器如下所示:
在这里,在addInterceptor方法中我们将创建的拦截器传进去了。接下来我们看如何创建拦截器。fun myHttpClient(): OkHttpClient { val builder = OkHttpClient().newBuilder() .addInterceptor(/*our interceptor*/) return builder.build() }
5. 创建拦截器
- 为了创建拦截器,你需要创建一个类实现Interceptor接口如下所示:
这里,interceptor(),我们能够执行任何想要的操作。为了使用拦截器我们能够如下这样使用:class MyInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { /** * Our API Call will be intercepted here */ } }
fun myHttpClient(): OkHttpClient { val builder = OkHttpClient().newBuilder() .addInterceptor(MyInterceptor()) return builder.build() }
我们能够添加MyInterceptor在addInterceptor();
接下来我们讨论更多的使用拦截器的实例。
6.使用拦截器的真实实例
-
下面是一些Android中常用的实例
-
集中打印错误日志
- 首先我们需要创建ErrorInterceptor如下:
class ErrorInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request: Request = chain.request() val response = chain.proceed(request) when (response.code()) { 400 -> { //Show Bad Request Error Message } 401 -> { //Show UnauthorizedError Message } 403 -> { //Show Forbidden Message } 404 -> { //Show NotFound Message } // ... and so on } return response } }
- 首先,我们获取request通过chain.request()
- 然后,我们获取从服务器返回的response,通过**chain.process(request)**传入request
- 现在,我们能够检查响应码然后做一个操作
- 我们能够传送这个错误到对应的视图通过使用接口, 或是类似与RxJava , EventBus ,等等;
- 假设我们得到一个401的错误等等。未经授权然后我们能够执行一个清除app数据/注销用户或是我们想要执行的其他任何操作。
- 首先我们需要创建ErrorInterceptor如下:
-
现在,为了在OkHttpClient中使用这个ErrorInterceptor,我们能够添加它如下:
.addInterceptor(ErrorInterceptor())
这就是使用拦截器怎样创建一个集中的错误日志。Okhttp有一个内置的对调试非常的有用日志。
想了解更多关于内置日志的信息,点击这里 -
注意:如果你想打印URL重定向的详细信息,考虑使用网络层的拦截器通过使用addNetworkInterceptor().
-
-
缓存服务器响应
- 如果我们想缓存API调用的响应以便我们能够再次调用这个API的时候,响应是从缓存中取出的。
- 假设我们有一个从客户端到服务端的API调用并且服务器开启了缓存控制(Cache-Control)的头标志,然后OkHttp的核心库可以识别这个头标志并且缓存特定时间从服务器发送的响应。
- 但是即使Cache-Control没有被服务器开启。我们也能够通过使用拦截器缓存从Okhttp Client返回的响应。
从上图中可以看到,我们 要做的是,在响应进入Okhttp的核心之前拦截从服务器的响应然后添加(Cache-Control)的头标志,因此这响应(带有cache-control头)将被OkHttpCore视为来自于服务器,Okhttp Core能够识别这个这个头标志然后缓存特定时间从服务器发送的响应。 - 我们创建一个拦截器如下所示:
在这里,我们有一个Cache-Control,用于提供给Cache-Control提供标志头。class CacheInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val response: Response = chain.proceed(chain.request()) val cacheControl = CacheControl.Builder() .maxAge(10, TimeUnit.DAYS) .build() return response.newBuilder() .header("Cache-Control", cacheControl.toString()) .build() } }
最后,我们能够添加如下:.addNetworkInterceptor(CacheInterceptor())
这里,我们看到,我们没有使用addInterceptor() 而是使用addNetworkInterceptor() 作为使用例子。这是因为这个操作发生在网络层。
- 但是,我们在构建离线App时候,我们需要考虑一些重要的事情。
- 这个缓存的响应只会在网络可用的时候才会返回因为OkHttp就是这样设计的。
- 当网络可用并且数据被缓存了,将会从缓存中返回数据。
- 即使当数据被缓存然而网络不可用,也将返回“无网络可用”的错误
- 接下来做什么?
- 除了上面的方法(CacheInterceptor ,仅仅当服务器没有开启缓存的时候使用)我们能够使用如下的ForceCacheInterceptor在应用层。我们创建如下的一个ForceCacheInterceptor去实现这个代码:
class ForceCacheInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val builder: Request.Builder = chain.request().newBuilder() if (!IsInternetAvailable()) { builder.cacheControl(CacheControl.FORCE_CACHE); } return chain.proceed(builder.build()); } }
- 我们能够添加拦截器在OkHttpClient如下:
在这里,我们添加ForceCacheInterceptor到OkHttpClient使用addInterceptor而不是使用addNetworkInterceptor()是因为我们想要在应用层面上工作。.addNetworkInterceptor(CacheInterceptor()) // 仅仅当服务器没有开启的缓存的时候使用 .addInterceptor(ForceCacheInterceptor())
- 除了上面的方法(CacheInterceptor ,仅仅当服务器没有开启缓存的时候使用)我们能够使用如下的ForceCacheInterceptor在应用层。我们创建如下的一个ForceCacheInterceptor去实现这个代码:
-
集中添加类似认证令牌的标志头
- 假设我们要使用API调用需要在所有的调用中添加认证头。我们能够单独使用它也可以集中使用拦截器
class AuthTokenInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() val requestBuilder = originalRequest.newBuilder() .header("Authorization", "AuthToken") val request = requestBuilder.build() return chain.proceed(request) } }
- 首先,我们获取token从我们的本地存储例如SharedPreference
- 接着,我们拦截使用chain.request()从应用程序触发的最原始的请求然后设置它到最原始的请求originalRequest。
- 然后,我们构建一个请求添加头通过网络请求需要的键值对形式Header
- 接着,我们再次构建这个请求并且返回给响应请求通过*chain.proceed(request)*传递带有认证头信息的请求。
- 这就是怎样在API调用的时候集中处理常用的标志头。我们要使用这种拦截器,我们将做如下操作:
.addInterceptor(AuthTokenInterceptor())
- 假设我们要使用API调用需要在所有的调用中添加认证头。我们能够单独使用它也可以集中使用拦截器
-
在单个位置刷新访问令牌
- 假设我们在实例中我们在ErrorInterceptor中有一个401的错误,我们需要刷新一个认证令牌因为我们有了认证失败的错误。我们可以做如下操作:
override fun intercept(chain: Interceptor.Chain): Response { val accessToken = //our access Token val request = chain.request().newBuilder() .addHeader("Authorization", accessToken) .build() val response = chain.proceed(request) if (response.code() == 401) { val newToken: String = //fetch from some other source 获取其他资源 if (newToken != null) { val newRequest = chain.request().newBuilder() .addHeader("Authorization", newToken) .build() return chain.proceed(newRequest) } } return response }
- 如我们有响应码为401,未经授权的,我们将在这里刷新令牌,然后通过添加新标头修改请求并向服务器发出新请求。
- 注意:在刷新访问令牌时,另一种更灵活的方法是使用OkHttp的Authenticator接口
- 假设我们在实例中我们在ErrorInterceptor中有一个401的错误,我们需要刷新一个认证令牌因为我们有了认证失败的错误。我们可以做如下操作:
-
在Android端启用Gzip
- Gzip被用于数据压缩。在Android端同样也可以通过Interceptor使用Gzip压缩。当我们获得一个响应,OkHttp自动识别这个头标志(conent encoding)和解压缩数据返回,但是假设我要发送一个压缩数据给服务器,我们就不得不书写我们自己的拦截器。
你可以找到GzipRequestInterceptord点击这里
同样你需要添加interceptor如下:public class GzipRequestInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) { return chain.proceed(originalRequest); } Request compressedRequest = originalRequest.newBuilder() .header("Content-Encoding", "gzip") .method(originalRequest.method(), forceContentLength(gzip(originalRequest.body()))) .build(); return chain.proceed(compressedRequest); } private RequestBody forceContentLength(final RequestBody requestBody) throws IOException { final Buffer buffer = new Buffer(); requestBody.writeTo(buffer); return new RequestBody() { @Override public MediaType contentType() { return requestBody.contentType(); } @Override public long contentLength() { return buffer.size(); } @Override public void writeTo(BufferedSink sink) throws IOException { sink.write(buffer.snapshot()); } }; } private RequestBody gzip(final RequestBody body) { return new RequestBody() { @Override public MediaType contentType() { return body.contentType(); } @Override public long contentLength() { return -1; // We don't know the compressed length in advance! } @Override public void writeTo(BufferedSink sink) throws IOException { BufferedSink gzipSink = Okio.buffer(new GzipSink(sink)); body.writeTo(gzipSink); gzipSink.close(); } }; } }
至此,这些是实际的用例,我们如何在安卓应用中使用拦截器。我们可以用拦截器做很多事情。让我们开始充分利用它。.addInterceptor(GzipRequestInterceptor())
- Gzip被用于数据压缩。在Android端同样也可以通过Interceptor使用Gzip压缩。当我们获得一个响应,OkHttp自动识别这个头标志(conent encoding)和解压缩数据返回,但是假设我要发送一个压缩数据给服务器,我们就不得不书写我们自己的拦截器。