• 优雅地使用Retrofit+RxJava(二)


    前言

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

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

    我自己平时对代码的简洁性要求非常高,所以retrofit+rxjava正好切中了我的痛点,这也是激发我写这篇文章的原因,我想要与大家一起交流进步,能够看看我的代码演示样例

    一个简单的演示样例

    (能够选择先忽略,等看完这篇文章再回头来看)

    /**
    * @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的学习成本还是比較高的。

    举个样例,就拿数据打印来说,假设使用okHttp的话,能够直接在回调里面打印server返回的json数据,可是放在retrofit中。由于retrofit会自己主动帮你封装成相应的bean,这使得数据解析这个过程不可见。需要通过retrofit的拦截器才干实现,所以拦截器对于retrofit来说,是一个非常非常重要的东西。

    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,有的话取出并存在本地。先上代码:

    /**
     * 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我会说到。

    加密解密拦截器

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

    拦截器的代码例如以下:

    /**
    * @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哦!

  • 相关阅读:
    第七次——例行报告
    贪吃蛇功能说明书(初稿)
    第六周——例行报告
    第五周——例行报告
    贪吃蛇界面设计初稿
    贪吃蛇需求分析
    软件工程第三次作业
    软件工程第二次作业
    软件工程第一次作业
    Python基础综合练习修改
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8334002.html
Copyright © 2020-2023  润新知