前言

在我上一篇讲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过期处理

这里的处理逻辑事实上还蛮复杂的,看看下图(画的比較丑,不要介意)

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXExMjI2MjcwMTg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

在这里能够使用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哦!

优雅地使用Retrofit+RxJava(二)的更多相关文章

  1. retrofit+RXjava二次封装

    接入说明:项目中已集成RXjava,RXandroid.Retrofit,为避免包冲突,不须要再次接入. 就可以直接使用RXjava,Retrofit的所有api. github地址:https:// ...

  2. Android Retrofit+RxJava 优雅的处理服务器返回异常、错误

    标签: 开始本博客之前,请先阅读: Retrofit请求数据对错误以及网络异常的处理 异常&错误 实际开发经常有这种情况,比如登录请求,接口返回的 信息包括请求返回的状态:失败还是成功,错误码 ...

  3. Retrofit + RxJava + OkHttp 让网络请求变的简单-基础篇

    https://www.jianshu.com/p/5bc866b9cbb9 最近因为手头上的工作做完了,比较闲,想着做一些优化.看到以前用的那一套网络框架添加一个请求比较麻烦,并且比较难用,所以想改 ...

  4. 基于Retrofit+RxJava的Android分层网络请求框架

    目前已经有不少Android客户端在使用Retrofit+RxJava实现网络请求了,相比于xUtils,Volley等网络访问框架,其具有网络访问效率高(基于OkHttp).内存占用少.代码量小以及 ...

  5. Android 网络请求Retrofit + RxJava

    一.背景 经常看到项目用Retrofit+RxJava+RxAndroid的框架,为了看懂项目的结构.现在来了解一下,Retrofit: Retrofit是Square 公司开发的一款正对Androi ...

  6. 利用Retrofit, RxJava获取网络内容

    Retrofit & RxJava 关于如何使用Retrofit和RxJava请阅读参考中的两篇文章. Retrofit处理数据 Retrofit是在什么时候处理从网络中获取到的json数据的 ...

  7. 结合Retrofit,RxJava,Okhttp,FastJson的网络框架RRO

    Retrofit以其灵活的调用形式, 强大的扩展性著称. 随着RxAndroid的推出, Retrofit这样的可插拔式的网络框架因其可以灵活兼容各种数据解析器, 回调形式(主要还是RxJava啦)而 ...

  8. 设计模式笔记之四:MVP+Retrofit+RxJava组合使用

    本博客转自郭霖公众号:http://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650236866&idx=1&sn=da66 ...

  9. Android MVP开发模式及Retrofit + RxJava封装

    代码已上传到Github,因为接口都是模拟无法进行测试,明白大概的逻辑就行了! 欢迎浏览我的博客--https://pushy.site 1. MVP模式 1.1 介绍 如果熟悉MVP模式架构的话,对 ...

随机推荐

  1. List与array的相互转换

    1.List->Array 调用List的toArray方法 List<String> list = new ArrayList<String>(); list.add( ...

  2. 【VC编程技巧】窗口☞3.4利用bitmap改变对话框的背景。

    效果图: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2hlbl9qaW50/font/5a6L5L2T/fontsize/400/fill/I0JBQ ...

  3. jquery源码03 (3184 , 3295) support : 功能检测

    <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content ...

  4. | 插件下载陈磊SQL MD5 加密

    简介:SQL MD5 加密 下述是 SQL Server 中 MD5加密 16位和32位的 ,)) ,ModifiedOn=null ; ,)) ,ModifiedOn=null ;

  5. Nabou应用实例

      本文接上文 <完整性检查工具Nabou> http://chenguang.blog.51cto.com/350944/280712650) this.width=650;" ...

  6. Android Gson解析json工具类封装

    package com.springSecurity.gson; import java.util.ArrayList; import java.util.List; import java.util ...

  7. 关于cook操作

    http://www.cnblogs.com/fishtreeyu/archive/2011/10/06/2200280.html

  8. Codefroces Educational Round 27 845G Shortest Path Problem?

    Shortest Path Problem? You are given an undirected graph with weighted edges. The length of some pat ...

  9. 2018 java实训总结(时间戳&&主键)

    java实训题目:源管理系统. 答辩的时候被老师怼了以下几个的地方: 1.主键改变了 2.没时间戳却说自己的程序里有先后(这就是老师迂腐了,主键自增可以间接反馈出他加入的早晚,即使主键做出了改变但只是 ...

  10. 清除celery 任务队列

    celery 有密码的时候 清除任务 redis-cli -h host -p port -a password -n 11 ltrim transcode 0 196 没有密码的时候 redis-c ...