Android之okhttp拦截器的使用

上周在部门的技术分享会上简单分享了一下使用okhttp拦截器的使用,趁着端午假期好好将内容整理整理,希望能够帮助到其他的朋友

okhttp拦截器主要在以下几种情况使用:

  1. 网络请求、响应日志输出
  2. 在Header中统一添加cookie、token
  3. 设置网络缓存

使用前准备

相信现在大部分做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);// 失败重连

注意这里我们添加网络缓存拦截器时是用来addInterceptoraddNetworkInterceptor这两个方法,那么这两个方法有什么不一样呢?

addInterceptor 添加应用拦截器

  1. 不需要担心中间过程的响应,如重定向和重试.
  2. 总是只调用一次,即使HTTP响应是从缓存中获取.
  3. 观察应用程序的初衷. 不关心OkHttp注入的头信息如: If-None-Match.
  4. 允许短路而不调用 Chain.proceed(),即中止调用.
  5. 允许重试,使 Chain.proceed()调用多次.

addNetworkInterceptor 添加网络拦截器
1. 能够操作中间过程的响应,如重定向和重试
2. 当网络短路而返回缓存响应时不被调用.
3. 只观察在网络上传输的数据
4. 携带请求来访问连接

至此,okhttp拦截器的主要几种使用场景以及介绍完毕,文中如有错误、不当之处,还请各位同学指出,共同进步。

参考文章:

https://blog.csdn.net/tangxl2008008/article/details/51887285

猜你喜欢

转载自blog.csdn.net/zw791029369/article/details/80723377