前言

在我上一篇讲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. POJ 2352 Stars(线段树)

    题目地址:id=2352">POJ 2352 今天的周赛被虐了. . TAT..线段树太渣了..得好好补补了(尽管是从昨天才開始学的..不能算补...) 这题还是非常easy的..维护 ...

  2. hdu1856 More is better (并查集)

    More is better Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 327680/102400 K (Java/Others) ...

  3. 用c#编写爬虫在marinetraffic下载船仅仅图片

    近期在做船仅仅识别方面的事情,须要大量的正样本来训练adaboost分类器. 于是到marinetraffic这个站点上下载船仅仅图片.写个爬虫来自己主动下载显然非常方便. 站点特点 在介绍爬虫之前首 ...

  4. 关于Java IO InputStream 的一点整理!

    程序的开发其中一直在用文件的读写.可是对于java其中输入流以及输出流仅仅是会用不理解,一直以来想搞清楚其,可是一直没有运行(悲剧).今天早上抽出半个小时通过JDK API1.6.0中文版帮助逐步的了 ...

  5. IsoAlgo3d - IDF/PCF pipeline 3d viewer

    IsoAlgo3d - IDF/PCF pipeline 3d viewer eryar@163.com Key Words. IDF, PCF, IsoAlgo, 3D 当前国际主流管道设计软件都可 ...

  6. 【基础篇】Android MediaPlayer基本使用方式

    使用MediaPlayer播放音频或者视频的最简单例子: JAVA代码部分: public class MediaPlayerStudy extends Activity { private Butt ...

  7. LuoguP4012 深海机器人问题(费用流)

    题目描述 深海资源考察探险队的潜艇将到达深海的海底进行科学考察. 潜艇内有多个深海机器人.潜艇到达深海海底后,深海机器人将离开潜艇向预定目标移动. 深海机器人在移动中还必须沿途采集海底生物标本.沿途生 ...

  8. python hmac 加密

    python2 :  key 是秘钥 类型为 str msg 要加密的文件 str digestmod 要加密的方式 python3: key 是秘钥 类型为 byte msg 要加密的文件 byte ...

  9. 使用pandas导出PostgreSQL 模式下的所有表数据并保存

    PostgreSQL PostgreSQL 是一个非常强大的数据库,它是一个免费的对象-关系数据库服务器(数据库管理系统).PostgreSQL支持大部分 SQL 标准, 在语句上也有很大的相似的地方 ...

  10. Swift视频教程,Swift千人学iOS开发编程语言

    此时大家站在同一起跑线.Swift语言将将是下一个风靡程序猿界的编程语言,是否能抢占先机,近在咫尺. 本期推荐Swift编程语言视频教程,内容包含:开发环境基本使用.数据类型和常量.数据自己主动检查和 ...