上周在部门的技术分享会上简单分享了一下使用okhttp拦截器的使用,趁着端午假期好好将内容整理整理,希望能够帮助到其他的朋友
okhttp拦截器主要在以下几种情况使用:
- 网络请求、响应日志输出
- 在Header中统一添加cookie、token
- 设置网络缓存
使用前准备
相信现在大部分做android开发的同学们都是使用Retrofit 作为网络框架,而Retrofit 内部请求也是基于 OKHttp 的,所以我们先在module的build.gradle添加如下依赖:
/*Retrofit2所需要的包*/
implementation 'com.squareup.retrofit2:retrofit:2.1.0'
//okhttp日志拦截器
implementation 'com.squareup.okhttp3:logging-interceptor:3.3.0'
//ConverterFactory的Gson依赖包
api 'com.squareup.retrofit2:converter-gson:2.1.0'
//ConverterFactory的String依赖包
implementation 'com.squareup.retrofit2:converter-scalars:2.1.0'
//rxjava
implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
implementation 'io.reactivex:rxjava:1.2.1'
添加日志拦截器
我们简单的对retrofit封装一下,新建一个类,类名为RetrofitClient,这里采用静态内部类的方式来实现单例。
private static class InstanceHolder {
private static final RetrofitClient instance = new RetrofitClient();
}
public static RetrofitClient getInstance(Context context) {
mContext = context.getApplicationContext();
return InstanceHolder.instance;
}
然后我们在RetrofitClient的私有构造函数中构建日志拦截器
private RetrofitClient() {
//日志拦截器
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(CONNECT_TIME_OUT, TimeUnit.SECONDS)
.readTimeout(READ_TIME_OUT, TimeUnit.SECONDS)//设置读取超时时间
.writeTimeout(WRITE_TIME_OUT, TimeUnit.SECONDS)//设置写的超时时间
.retryOnConnectionFailure(true);// 失败重连
if (IS_DEBUG) {
builder.addNetworkInterceptor(loggingInterceptor);
}
mOkHttpClient = builder.build();
mRetrofit = new Retrofit.Builder()
.client(mOkHttpClient)
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())// 增加返回值为String的支持
.addConverterFactory(GsonConverterFactory.create())// 增加返回值为Gson的支持(以实体类返回)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
我们通过new HttpLoggingInterceptor()方法直接获得到一个okhttp日志拦截器对象,然后给其设置日志的输出等级,我们查看okhttp中指定的等级
public enum Level {
/** No logs. */
NONE,
BASIC,
HEADERS,
BODY
}
是一个枚举类型,各自对应的意思相信大家很容易就能理解,我不再赘述。回到正题我们这里设置日志等级为body,然后给ohttp添加上该日志拦截器。
if (IS_DEBUG) {
builder.addNetworkInterceptor(loggingInterceptor);
}
这样我们在之后的调试中可以通过logcat很方便的看见我们发出的request中包含的所有信息,包括head、对应的请求地址、请求参数以及对应的响应request。
大家可以看见我在这使用了一个boolean类型的变量IS_DEBUG 来控制是否添加日志拦截器,这是因为我偶然在一次做版本更新时遇到了一个问题,有两根网线,其中一根可能因为是从交换机中分出来的带宽不太够,结果导致同一个程序在接入带宽大的那个终端时一切正常,结果在带宽小的那个终端上却出现了下载时程序崩溃的情况,最后查看崩溃日志发现内存溢出了,最终排查发现居然是因为添加了okhttp日志拦截器,下载时日志不断输出导致内存溢出的原因,真是坑爹啊…..所以我建议在程序打包的时候将日志拦截器关闭,避免掉坑,
小提示:
okhttp日志拦截器log输出的默认TAG是“==》okhttp”,有时我们查看日志可能不太方便,想自己改一下TAG,这是可以在new HttpLoggingInterceptor()时重写一下log方法:
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
Log.i("这里自定义TAG:",message);
}
});
在Header中统一添加cookie、token
在web中客户端和服务器通信时,为了保证服务器知道这是哪个客户端发送过来的请求,所以常常需要客户端在Header中添加cookie。而在我们Android当中虽然可以将当前用户信息保存到本地,然后将客户唯一标识以参数的形式传给后台来进行通信,但是这种方式还是过于直白,不太安全,一旦接口暴露了,服务器很容易受到攻击。所以为了提升接口的安全性,一般情况我们也都会设置cookie或者token来验证客户端的请求合法性。
Retrofit 中我们可以通过 @Header 这个注解来修饰参数满足我们的需求
@FormUrlEncoded
@POST("/communicate/getPerson")
abstract Call<ResponseBody> login(@Header("token") String token, @Field("userId") String userId);
但是这样每个方法我们都要这样额外添加一个参数,未免显得太费劲、繁琐了,为了更好的偷懒,我们可以使用拦截器来实现这个功能。我们新建一个类HeaderInterceptor 实现 Interceptor 接口,重写它的intercept方法。
public class HeaderInterceptor implements Interceptor {
private Context context;
public HeaderInterceptor(Context context) {
this.context = context;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
//从本地读取存储的cookie和token
String cookie = (String) SpfUtils.get(context, "cookie", "");
String token = (String) SpfUtils.get(context, "token", "");
Log.i("=====Interceptor=>", "cookie = " + cookie);
Log.i("=====Interceptor=>", "token = " + token);
builder.addHeader("token", token);
builder.addHeader("Cookie", cookie);
return chain.proceed(builder.build());
}
SpfUtils是一个简单的SharedPreferences工具类,我们在登录时可以获取到cookie或者token,然后将cookie、token使用SharedPreferences保存到本地,以下是通过response获取cookie的伪代码,可供参考。
/** 这里获取登录请求返回的cookie */
List<String> headers = response.headers("Set-Cookie");
if (!headers.isEmpty()) {
StringBuffer cookieBuffer = new StringBuffer();
for (String str : headers) {
cookieBuffer.append(str).append(";");
}
String a=cookieBuffer.toString();
SpfUtils.put(context, "cookie", cookieBuffer.toString());
}
然后回到我们简单封装的RetrofitClient类当中,稍作更改
//Header拦截器
HeaderInterceptor headerInterceptor = new HeaderInterceptor(mContext);
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(CONNECT_TIME_OUT, TimeUnit.SECONDS)
.readTimeout(READ_TIME_OUT, TimeUnit.SECONDS)//设置读取超时时间
.writeTimeout(WRITE_TIME_OUT, TimeUnit.SECONDS)//设置写的超时时间
.addInterceptor(headerInterceptor)// 添加拦截器,存取cookie
.retryOnConnectionFailure(true);// 失败重连
这样我们在登录成功获取到cookie或者token保存到本地之后,实现了cookie、token的持久化,在以后的接口中就可以不用重复使用@Header的方式来添加cookie、token了。
设置网络缓存
在有些对网络要求比较高的情况,可能需要针对有网、无网情况分别做处理,这时我们也可以通过设置网络缓存拦截器的方式来实现。
首先我们设置一下缓存的路径
File httpCacheDirectory = new File(context.getCacheDir(), "responses");//设置缓存路径
Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);
然后新建一个类NetworkInterceptor实现Interceptor接口
public class NetworkInterceptor implements Interceptor {
private Context context;
private String HEADER="Mozilla/5.0 (Android;) AppleWebKit/537.36 (KHTML, like Gecko) Version/ally1.0 Mobile Safari/537.36";
public NetworkInterceptor(Context context) {
this.context = context;
}
@Override
public Response intercept(Chain chain) throws IOException {
//先获取到本次的request
Request request = chain.request();
// if (CommonUtils.isNetworkConnected(context)){// 有网的时候走网络数据
// request = request.newBuilder()
// .cacheControl(CacheControl.FORCE_NETWORK)
// .build();
// }else{// 没网的时候走缓存数据
// request=request.newBuilder()
// .cacheControl(CacheControl.FORCE_CACHE)
// .build();
// }
//没网的时候走缓存数据
if (!CommonUtils.isNetworkConnected(context)) {
request=request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
//执行请求,获取到响应
Response response = chain.proceed(request);
// if (CommonUtils.isNetworkConnected(context)){
// int maxAge = 0 * 60;
// // 有网络时 设置缓存超时时间0个小时
// response.newBuilder()
// .header("Cache-Control", "public, max-age=" + maxAge)
// .removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
// .build();
// }else{
// // 无网络时,设置超时为一年
// int maxStale = 60 * 60 * 24 * 365;
// response.newBuilder()
// .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
// .removeHeader("Pragma")
// .build();
// }
// return response;
return response.newBuilder()
.removeHeader("Pragma")
.build();
}
然后再对我们的RetrofitClient稍作更改
File httpCacheDirectory = new File(mContext.getCacheDir(), "responses");//设置缓存路径
Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(CONNECT_TIME_OUT, TimeUnit.SECONDS)
.readTimeout(READ_TIME_OUT, TimeUnit.SECONDS)//设置读取超时时间
.writeTimeout(WRITE_TIME_OUT, TimeUnit.SECONDS)//设置写的超时时间
.addInterceptor(headerInterceptor)// 添加拦截器,存取cookie
.cache(cache)// 设置Cache目录
//走缓存,两个都要设置
.addInterceptor(new NetworkInterceptor(mContext))
.addNetworkInterceptor(new NetworkInterceptor(mContext))
.retryOnConnectionFailure(true);// 失败重连
注意这里我们添加网络缓存拦截器时是用来addInterceptor和addNetworkInterceptor这两个方法,那么这两个方法有什么不一样呢?
addInterceptor 添加应用拦截器
- 不需要担心中间过程的响应,如重定向和重试.
- 总是只调用一次,即使HTTP响应是从缓存中获取.
- 观察应用程序的初衷. 不关心OkHttp注入的头信息如: If-None-Match.
- 允许短路而不调用 Chain.proceed(),即中止调用.
- 允许重试,使 Chain.proceed()调用多次.
addNetworkInterceptor 添加网络拦截器
1. 能够操作中间过程的响应,如重定向和重试
2. 当网络短路而返回缓存响应时不被调用.
3. 只观察在网络上传输的数据
4. 携带请求来访问连接
至此,okhttp拦截器的主要几种使用场景以及介绍完毕,文中如有错误、不当之处,还请各位同学指出,共同进步。
参考文章:
https://blog.csdn.net/tangxl2008008/article/details/51887285