优雅地使用Retrofit+RxJava(二)

前言

在我上一篇讲Retrofit+RxJava在MVP模式中优雅地处理异常(一)中,发现很是多网友发邮箱给我表示期待个人下一篇文章,正好趁着清明假期。我就写写平时我在使用RxJava+Retrofit怎么去灵活地处理一些场景。比方说一些比較常见的场景:php

  • 网络请求过程当中token的处理
  • 网络请求数据的加密与解密
  • 为每个请求加入固定的头部。比方说当前版本,Rsa的密钥等等
  • 规范化每个网络请求,让代码仅仅写一次

我本身平时对代码的简洁性要求很是高,因此retrofit+rxjava正好切中了个人痛点,这也是激发我写这篇文章的缘由,我想要与你们一块儿交流进步,可以看看个人代码演示样例css

一个简单的演示样例

(可以选择先忽略,等看完这篇文章再回头来看)java

/** * @author whaoming * github:https://github.com/whaoming * created at 2017/2/14 15:59 * Description:数据请求的管理类 */
public class HttpMethods {
    //retrofit相应的接口
    private ApiService myService;

    //构造方法私有
    private HttpMethods() {
        List<Interceptor> interceptors = new ArrayList<>();
        Map<String,String> headers = new HashMap<>();
        headers.put("userid",25);
        TokenGetInterceptor tokenGetInterceptor = new TokenGetInterceptor(headers);
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        AESInterceptor aesInterceptor = new AESInterceptor();
         //建立一个http头部处理器拦截器(这里主要处理server返回token的捕获)
        interceptors.add(tokenGetInterceptor );
        //日志打印拦截器
        interceptors.add(loggingInterceptor );
        //数据的加密与解密拦截器
        interceptors.add(aesInterceptor);

        RetrofitHelper.getInstance().init(ConstantValue.SERVER_URL,interceptors );
        //建立service
        myService = RetrofitHelper.getInstance().createService(ApiService.class);
    }

    //依据id用户一个用户的信息
    public Observable<UserCommonInfo> getUserInfoById(int userid){
        return  Direct2.create(myService.getUserInfoById(userid),new TokenProviderImpl());
    }
}

/** * Created by Mr.W on 2017/2/14. * E-maiil:122627018@qq.com * github:https://github.com/whaoming * TODO: 依照建立者模式的思想。把一个訪问server的操做规格化 */
public class Direct {
   public static<T> Observable<T> create(Observable<Result<T>> resurce,TokenProvider tokenProvider){
       return resurce
               //解析固定格式json
               .map(new ResultParseInterceptor<T>())
               //处理token过时,tokenProvider为当发现token过时时候详细的处理方式
               .retryWhen(new TokenExpireInterceptor(tokenProvider))
               //捕获整个请求过程当中的错误
               .onErrorResumeNext(new ErrorInterceptor<T>())
                .observeOn(AndroidSchedulers.mainThread())
               .subscribeOn(Schedulers.io());
   }
}

网络层:RxJava+Retrofit

相对来讲。retrofit+rxjava的学习成本仍是比較高的。git

举个样例,就拿数据打印来讲,假设使用okHttp的话,可以直接在回调里面打印server返回的json数据,但是放在retrofit中。因为retrofit会本身主动帮你封装成相应的bean,这使得数据解析这个过程不可见。须要经过retrofit的拦截器才干实现,因此拦截器对于retrofit来讲,是一个很是很是重要的东西。github

retrofit拦截器的使用场景

日志拦截器

还记得刚開始使用retrofit的时候,就被这个功能吓到了,大哥我仅仅是想简单地打印下server给了我什么数据,为何要这么麻烦啊。。!只是后面也愈来愈理解retrofit这样作的缘由了,(我的愚见)这样使得所有的操做都规范化。用我本身的话说。就是retrofit告诉你,仅仅要你想要”入侵”数据发送和解析的过程,不管是什么操做,你就得给我使用拦截器。那么事实上说难也不难。仅仅是几行代码而已:算法

HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                try {
                    String text = URLDecoder.decode(message, "utf-8");
                    Log.d("OKHttp", text);
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    Log.d("OKHttp", message);
                }
            }
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient =builder.build();
mRetrofit = new Retrofit.Builder()
                .baseUrl(baseURL)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .client(okHttpClient)
                .build();

token拦截器

token机制我相信大多数client都必须要有的一个东西,这里咱们这个拦截器的工做是为每个请求加入头部,还有拦截server返回的头信息里面是否包括token,有的话取出并存在本地。先上代码:json

/** * Created by Mr.W on 2017/2/6. * E-maiil:122627018@qq.com * github:https://github.com/whaoming * TODO: 拦截server返回的token并进行保存,并且在发起请求的时候本身主动为头部加入token */
public class TokenGetInterceptor implements Interceptor {

    private Map<String,String> headers = null;
    public TokenGetInterceptor(Map<String,String> headers){
        this.headers = headers;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request newRequest;
        if (headers!=null || !Account.isShortCookieEmpty()) {        
            Request.Builder builder = chain.request().newBuilder();
            if(headers!=null){
                for(Map.Entry<String,String> item : headers.entrySet()){
                    //加入一些其它头部信息,好比appid,userid等。由外部传入
                    builder.addHeader(item.getKey(),item.getValue());
                }
            }
            if (!Account.isShortCookieEmpty()) {
                builder.addHeader("token", Account.getShortCookie());
            }
            newRequest = builder.build();
        } else {
            newRequest = chain.request().newBuilder()
                    .build();
        }
        Response response = chain.proceed(newRequest);
        if (response.header("token") != null) {
            //发现短token。保存到本地
            Account.updateSCookie(response.header("token"));
        }
        String long_token = response.header("long_token");
        if (long_token != null) {
            //发现长token,保存到本地
            Account.updateLCookie(long_token);
        }
        return response;
    }
}
/** 什么是长token,短token? 区分长token与短token的缘由是因为俩种token的算法与生效时间不同。当发现短token过时的时候,client会带上长token向server再次获取短token。而后再又一次发起请求。固然每个系统的token机制均可能不同。这里也可以看出retrofit可以很是灵活地处理很是多种状况 */

那么关于整个流程token的维护。包括发现token过时以后,怎么请求新token。怎么又一次发起请求。这些操做retrofit要配合rxjava来实现。后面关于rxjava我会说到。markdown

加密解密拦截器

在这里先简单讲一下个人加密机制,主要是经过rsa+aes,也就是client表单提交的数据,经过aes加密,而后aes的key再经过client本地保存的公钥进行加密(此公钥由server经过rsa算法生成,打包的时候保存在client本地)。把加密以后的key放在请求头里面,一块儿发送给server。网络

拦截器的代码例如如下:app

/** * @author whaoming * github:https://github.com/whaoming * created at 2017/2/6 10:13 * Description:对表单提交的数据进行aes加密 */
public class AESInterceptor implements Interceptor {

    public String key = "123456789aaaaaaa";
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        try {
            Request newRequest = null;
            if (request.body() instanceof FormBody) {
                //发现表单数据
                FormBody formBody = (FormBody) request.body();
                FormBody.Builder formBuilder = new FormBody.Builder();
                String keyMI = null;
                for (int i = 0; i < formBody.size(); i++) {
                    if (formBody.name(i).equals("param")) {
                        //对提交的表单数据进行加密
                        String json = AESUtil.encrypt(formBody.value(i), key);
                        if (!TextUtils.isEmpty(json)) {
                            formBuilder.add("data", json);
                            //对aes的key经过rsa公钥加密
                            RSAPublicKey pk = RSAKeyProvider.loadPublicKeyByStr(AppContext.getPublicKeyStore());
                            keyMI = RSAUtils.encryptByPublicKey(key,pk);
                        }
                    }else{
                        formBuilder.addEncoded(formBody.encodedName(i), formBody.encodedValue(i));
                    }
                }
                FormBody newFormBody = formBuilder.build();
                Request.Builder builder = request.newBuilder();
                if(!TextUtils.isEmpty(keyMI)){
                    //将加密后的aes的key放在头部
                    builder.header("key",keyMI);
                }
                newRequest = builder
                        .method(request.method(), newFormBody)
                        .removeHeader("Content-Length")
                        .addHeader("Content-Length", newFormBody.contentLength() + "")
                        .build();
            }
            Response response = chain.proceed(newRequest == null ? request : newRequest);
            String result = response.body().string();
            return response.newBuilder().body(ResponseBody.create(response.body().contentType(), result)).build();
        }catch (Exception e){
            e.printStackTrace();
        }
        return chain.proceed(request);
    }
}

Rxjava操做符的灵活使用

(ps:强烈建议读第一篇文章后再继续往下看:Retrofit+RxJava在MVP模式中优雅地处理异常(一)

返回数据的错误码统一解析

这里事实上就是第一篇博文的内容,传送门:Retrofit+RxJava在MVP模式中优雅地处理异常(一)

错误拦截

这里事实上也是在第一篇中讲过的内容。主要就是利用RxJava的onErrorResumeNext操做符来作错误的拦截,可以使整个网络訪问过程的错误都在一个地方解析。从而大大下降view层的工做量,并且使得view层与m层耦合度大大下降。灵活性提升,代码量大大下降。

/** * Created by Mr.W on 2017/2/14. * E-maiil:122627018@qq.com * github:https://github.com/122627018 * TODO: 异常解析的一个拦截器 */
public class ErrorInterceptor<T> implements Func1<Throwable, Observable<T>> {
    @Override
    public Observable<T> call(Throwable throwable) {
        throwable.printStackTrace();
        //ExceptionProvider:一个错误解析器
        return Observable.error(ExceptionProvider.handleException(throwable));
    }
}

token过时处理

这里的处理逻辑事实上还蛮复杂的,看看下图(画的比較丑,不要介意)
这里写图片描写叙述
在这里可以使用RxJava的retryWhen操做符。先看看server返回的数据格式:

/** * 这是server返回数据的一个固定格式 * @author Mr.W */
public class Result<T> {
    public int state;
    public String error;
    public T infos;
}

那么一个主要的流程是这种:
这里写图片描写叙述

因此retryWhen就可以在拦截错误的时候发挥做用,可以这样理解retryWhen。当发现onError事件的时候,在retryWhen内部:

  • 返回一个新的Observable,会触发又一次订阅
  • 返回Observable.onError,会继续原来的订阅事件

当发现错误码为500的时候。调用传入的接口(此接口用于token的又一次获取)

/** * Created by Mr.W on 2017/2/14. * E-maiil:122627018@qq.com * github:https://github.com/122627018 * TODO: 短token过时的处理 */ public class TokenExpireInterceptor implements Func1<Observable<?

extends Throwable>, Observable<?>> { TokenProvider tokenProvider; public TokenExpireInterceptor(TokenProvider tokenProvider){ this.tokenProvider = tokenProvider; } @Override public Observable<?> call(Observable<?

extends Throwable> observable) { return observable.flatMap(new Func1<Throwable, Observable<?

>>() { @Override public Observable<?

> call(Throwable throwable) { if(throwable instanceof ServerException){ ServerException ex = (ServerException)throwable; if(ex.getCode() == 500){ //发现token过时标识,调用获取token的接口 return tokenProvider.getToken(); } } return Observable.error(throwable); } }); } } /** * token又一次获取的接口 * Created by Mr.W on 2017/2/14. * E-maiil:122627018@qq.com * github:https://github.com/122627018 */ public interface TokenProvider { Observable<String> getToken(); }

这样就可以很是完美的处理了token过时的情景。关于token过时的处理

RxJava+Retrofit网络訪问流程的规范化

好了。到这里咱们总结一下上面咱们说到的点,那么事实上每个点都是我本身的项目中实际使用到的,可以看看如下这个业务逻辑:

这里写图片描写叙述
可以看出。在发出网络请求的时候的逻辑,都是由Retrofit的拦截器来实现的。那么在处理请求结果的时候,都是由RxJava来实现的。因此,整个逻辑就很是清晰很是舒服了

/** * Created by Mr.W on 2017/2/14. * E-maiil:122627018@qq.com * github:https://github.com/whaoming * TODO: 依照建立者模式的思想,把一个处理请求结果的操做流程化 */
public class Direct {
   public static<T> Observable<T> create(Observable<Result<T>> resurce,TokenProvider tokenProvider){
       return resurce
               //解析固定格式json
               .map(new ResultParseInterceptor<T>())
               //处理token过时,tokenProvider为详细的处理方式
               .retryWhen(new TokenExpireInterceptor(tokenProvider))
               //检查是否有错误
               .onErrorResumeNext(new ErrorInterceptor<T>())
                .observeOn(AndroidSchedulers.mainThread())
               .subscribeOn(Schedulers.io());
   }
}


/** * @author whaoming * github:https://github.com/whaoming * created at 2017/2/14 15:59 * Description:数据请求的管理类,负责建立请求 */
public class HttpMethods {
    //retrofit相应的接口
    private ApiService myService;

    //构造方法私有
    private HttpMethods() {
        List<Interceptor> interceptors = new ArrayList<>();
        Map<String,String> headers = new HashMap<>();
        headers.put("userid",25);
        TokenGetInterceptor tokenGetInterceptor = new TokenGetInterceptor(headers);
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        AESInterceptor aesInterceptor = new AESInterceptor();
         //建立一个http头部处理器拦截器(这里主要处理server返回token的捕获)
        interceptors.add(tokenGetInterceptor );
        //日志打印拦截器
        interceptors.add(loggingInterceptor );
        //数据的加密与解密拦截器
        interceptors.add(aesInterceptor);

        RetrofitHelper.getInstance().init(ConstantValue.SERVER_URL,interceptors );
        //建立service
        myService = RetrofitHelper.getInstance().createService(ApiService.class);
    }

    //依据id用户一个用户的信息
    public Observable<UserCommonInfo> getUserInfoById(int userid){
        return  Direct2.create(myService.getUserInfoById(userid),new TokenProviderImpl());
    }
}

总结

欢迎你们私信我交流一下,你们也可以看看下个人我的项目,关于我平时的一些文章分享到的技术,我基本都集成在上面:github地址
欢迎star哦!

相关文章
相关标签/搜索