App网络请求实战二:继续封装以及Interceptor拦截器的使用场景分析

App网络请求实战二:继续封装以及Interceptor拦截器的使用场景分析

我一猛龙撞击,加一手回笼望月,完美,叫你皮!

老规矩,先上图

这里写图片描述

OkHttp的配置

如果你还没有看上一篇,你可以先看一看上一篇 App网络请求实战一:Rxjava+Retrofit的初步封装

上一篇中遗留了一个问题就是:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(ApiService.baseUrl)
        //这里的client当然可以自己配置
        .client(new OkHttpClient())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())
        .build();

apiService = retrofit.create(ApiService.class);

就是我每次调用网络请求的时候都要写这么一大段,感觉有点zz有木有,而且还没有配置okhttp。所以我们还要再继续封装下。关于为什么retrofit.create(ApiService.class);就可以调用网络,这里面用到了动态代理的技术,不是实战主题,不展开。retrofit采用一个baseUrl对应一个retrofit原则,所以最好采用单例模式进行封装,如下所示:

/**
 * <pre>
 *     作者   : 肖坤
 *     时间   : 2018/04/20
 *     描述   :
 *     版本   : 1.0
 * </pre>
 */
public class RetrofitHelper
{
    //假设这个是默认的retrofit,因为一个App可能有多个baseUrl
    //所以可能有多个retrofit
    private static Retrofit retrofit1;

    private static Retrofit retrofit2;

    public static Retrofit getRetrofit1()
    {
        //设置gson解析不严格模式,防止一些解析错误,比如double数据出现NaN时
        Gson gson = new GsonBuilder()
                .setLenient()
                .create();
        OkHttpClient client = OkhttpHelper.initOkHttp1();
        if (retrofit1 == null)
        {
            retrofit1 = new Retrofit.Builder()
                    .client(client)
                    .baseUrl(ApiService.baseUrl)
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .build();
        }
        return retrofit1;
    }

    public static Retrofit getRetrofit2()
    {
        //设置gson解析不严格模式,防止一些解析错误,比如double数据出现NaN时
        Gson gson = new GsonBuilder()
                .setLenient()
                .create();
        OkHttpClient client = OkhttpHelper.initOkHttp2();
        if (retrofit2 == null)
        {
            retrofit2 = new Retrofit.Builder()
                    .client(client)
                    .baseUrl(ApiService.baseUrl)
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .build();
        }
        return retrofit2;
    }

    public static <S> S createService(Class<S> serviceClass)
    {
        return createService(serviceClass, getRetrofit1());
    }

    public static <S> S createService(Class<S> serviceClass, Retrofit retrofit)
    {
        if (retrofit == null)
        {
            throw new NullPointerException("retrofit 不能为null");
        }
        return retrofit.create(serviceClass);
    }
}

一般来说,中小型App都是只有一个baseUrl的。所以在创建ApiService时,默认retrofit1是主Retrofit。

关于OkHttpClinet的配置如下:

/**
 * <pre>
 *     作者   : 肖坤
 *     时间   : 2018/04/20
 *     描述   : okhttp配置
 *     版本   : 1.0
 * </pre>
 */
public class OkhttpHelper
{
    private static int CONNECT_TIME = 10;
    private static int READ_TIME = 20;
    private static int WRITE_TIME = 20;

    public static OkHttpClient initOkHttp1()
    {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();

        if (BuildConfig.DEBUG)
        {
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(BODY);
            //打印拦截器
            builder.addInterceptor(loggingInterceptor);
            //调试拦截器
            builder.addInterceptor(new StethoInterceptor());
        }

        File cacheFile = new File(Constants.PATH_CACHE);
        //最大50M,缓存太大领导有意见!为何你App占这么多内存?
        Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);
        //这里用到okhttp的拦截器知识
        builder.addInterceptor(appCacheInterceptor)
//                .addNetworkInterceptor(netCacheInterceptor)
                .cache(cache)
                //下面3个超时,不设置默认就是10s
                .connectTimeout(CONNECT_TIME, TimeUnit.SECONDS)
                .readTimeout(READ_TIME, TimeUnit.SECONDS)
                .writeTimeout(WRITE_TIME, TimeUnit.SECONDS)
                //失败重试
                .retryOnConnectionFailure(true)
                .build();
        return builder.build();
    }
}

封装之后在项目中我们就直接这么写了:

apiService = RetrofitHelper.createService(ApiService.class);

一行代码搞定,呜呜呜,感觉有点小委屈呢。

有两个比较有意思的拦截器一个是HttpLoggingInterceptor,这个拦截器可以打印出返回的数据,在debug期间很有用;第二个是StethoInterceptor调试拦截器,这个是facebook出的调试神器。OkHttp提供了拦截器机制,Interceptor又分为客户端拦截器和云端拦截器。如下所示:
这里写图片描述

通过addInterceptor添加的就是客户端拦截器,而通过addNetworkInterceptor添加的就是云端拦截器。客户端拦截器的好处就是:1.总是调用一次;2.允许重试和多次调用Chain.proceed()方法;云端拦截器的好处:不太清楚,没怎么用, 皮的我就不谈了!所以利用客户端拦截器的优势,我们可以用来做缓存处理和token刷新,

场景一:缓存的处理方式

这里写图片描述

其实逻辑很简单啦,所以我们可以根据上面的逻辑来编写代码。首先在拦截器中判断是否有网络, 没有网络的情况可以修改请求头中CacheControl属性,将其设定为只从缓存中获取数据。既然要有缓存,那必然需要缓存文件,不然丫的从哪里获取数据。所以需要给okhttp指定一个缓存文件夹通过cache来操作。如果有网络,那么不修改request请求头,直接走你!如下所示:

static Interceptor appCacheInterceptor = new Interceptor()
{
    @Override
    public Response intercept(Chain chain) throws IOException
    {
        Request request = chain.request();
        if (!SystemUtils.isNetworkConnected())
        {
            //强制使用缓存
            request = request.newBuilder()
                    .cacheControl(CacheControl.FORCE_CACHE)
                    .build();
        }
        int tryCount = 0;
        Response response = chain.proceed(request);
        while (!response.isSuccessful() && tryCount < 3)
        {
            tryCount++;
            // 重试
            response = chain.proceed(request);
        }
        return response;
    }
};

场景二:token刷新

​ 其实现在几乎所有的App都通过登录来拿token,然后app里除了登录接口外,其他的接口都需要在header里面验证token。有的App的token是不过期的,有的App的token是过期的。比如QQ一段时间不登陆,就会提示登录过期。所以,有过期token的App是有刷新token的需求的。

这里写图片描述

有时候我们需要根据业务来判断选择什么技术,脱离业务的技术就是扯淡。从上图中,我们可以得到当token过期的时候,我们需要执行两次请求。第一次请求后发现过期了,拿到新token,又进行了一次请求。所以这符合客户端本地拦截器的优势,直接选择本地拦截器来实现。

static Interceptor tokenInterceptor = new Interceptor()
{
    @Override
    public Response intercept(Chain chain) throws IOException
    {
        Request request = chain.request();
        Response response = chain.proceed(request);
        //判断token是否过期
        if (isTokenExpired(response))
        {
            //同步请求方式,获取新token
            ApiService service = RetrofitHelper.createService(ApiService.class);
            Call<BaseResponse<ResEntity1.DataBean>> call = service.getNewToken();
            retrofit2.Response<BaseResponse<ResEntity1.DataBean>> tokenRes = call.execute();
            String newToken = tokenRes.body().getData().getRes();
            //然后把这个新token存到sp中
            App.getSp().edit().putString("token", newToken).commit();
            Request newRequest = chain.request()
                    .newBuilder()
                    .header("token", newToken)
                    .build();
            response.body().close();//释放资源
            //重新请求
            return chain.proceed(newRequest);
        }
        //若没有过期,直接返回response
        return response;
    }
};

注意哦,这里面有一个坑坑的地方。那就是如何判断token是否过期这个点,一般是通过服务端返回的code来判断。所以我们需要拿到这个code,如下所示:

/**
 * 判断token是否过期
 *
 * @param response
 * @return
 */
private static boolean isTokenExpired(Response response)
{
    try
    {
        String bodyString = getBodyString(response);
        BaseResponse tokenExpiredData = new Gson().fromJson(bodyString, BaseResponse.class);
        int retCode = tokenExpiredData.getCode();
        if (retCode == Constants.EXPIRED_TOKEN)
        {
            return true;
        }
    } catch (IOException e)
    {
        e.printStackTrace();
    }
    return false;
}

/**
 * 将response转换为json字符串
 *
 * @param response
 * @return
 * @throws IOException
 */
public static String getBodyString(Response response) throws IOException
{
    ResponseBody responseBody = response.body();
    BufferedSource source = responseBody.source();
    source.request(Long.MAX_VALUE);
    Buffer buffer = source.buffer();
    Charset charset = Charset.forName("UTF-8");
    MediaType contentType = responseBody.contentType();
    if (contentType != null)
    {
        contentType.charset(charset);
    }
    //注意这里的方式,是仿写的HttpLoggingInterceptor
    //在okhttp中buffer只能被read一次,所以只能先clone然后在read
    //否则会报错
    return buffer.clone().readString(charset);
}

注意上面获取bodyString的时候不能直接使用response.body().string();方法,这样会报错。retrofit中规定那个buffer流只能read一次哦,所以需要先进行clone,然后再进行读取。HttpLoggingInterceptor打印拦截器中,有这方面的实现。

if (logHeaders) {
  Headers headers = response.headers();
  for (int i = 0, count = headers.size(); i < count; i++) {
    logger.log(headers.name(i) + ": " + headers.value(i));
  }

  if (!logBody || !HttpHeaders.hasBody(response)) {
    logger.log("<-- END HTTP");
  } else if (bodyEncoded(response.headers())) {
    logger.log("<-- END HTTP (encoded body omitted)");
  } else {
    BufferedSource source = responseBody.source();
    source.request(Long.MAX_VALUE); // Buffer the entire body.
    Buffer buffer = source.buffer();

    Charset charset = UTF8;
    MediaType contentType = responseBody.contentType();
    if (contentType != null) {
      charset = contentType.charset(UTF8);
    }

    if (!isPlaintext(buffer)) {
      logger.log("");
      logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
      return response;
    }

    if (contentLength != 0) {
      logger.log("");
      //就是在这里,对吧,我没蒙你吧!
      logger.log(buffer.clone().readString(charset));
    }

    logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
  }

拦截器真是一个好东西,这上面介绍的还是冰山一角呀0-0。未完待续。

以上。

擦,差点又忘了github地址:https://github.com/xiaokun19931126/HttpExceptionDemo

以上。

上篇博客:App网络请求实战一:Rxjava+Retrofit的初步封装
下篇博客:App网络请求实战三:下载文件以及断点续载

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值