优雅地使用Retrofit+RxJava(二)
来源:互联网 发布:网络监控巡检记录表 编辑:程序博客网 时间:2024/06/05 21:09
前言
在我上一篇讲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头部处理器拦截器(这里主要处理服务器返回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: 按照创建者模式的思想,把一个访问服务器的操作规格化 */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()); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
网络层:RxJava+Retrofit
相对来说,retrofit+rxjava的学习成本还是比较高的。举个例子,就拿数据打印来说,如果使用okHttp的话,可以直接在回调里面打印服务器返回的json数据,但是放在retrofit中,因为retrofit会自动帮你封装成对应的bean,这使得数据解析这个过程不可见,需要通过retrofit的拦截器才能实现,所以拦截器对于retrofit来说,是一个非常非常重要的东西。
retrofit拦截器的使用场景
日志拦截器
还记得刚开始使用retrofit的时候,就被这个功能吓到了,大哥我只是想简单地打印下服务器给了我什么数据,为什么要这么麻烦啊!!!不过后面也越来越理解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();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
token拦截器
token机制我相信大多数客户端都必须要有的一个东西,这里我们这个拦截器的工作是为每个请求添加头部,还有拦截服务器返回的头信息里面是否包含token,有的话取出并存在本地。先上代码:
/** * Created by Mr.W on 2017/2/6. * E-maiil:122627018@qq.com * github:https://github.com/whaoming * TODO: 拦截服务器返回的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过期的时候,客户端会带上长token向服务器再次获取短token,然后再重新发起请求。当然每个系统的token机制都可能不一样,这里也可以看出retrofit可以很灵活地处理很多种情况*/
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
那么关于整个流程token的维护,包括发现token过期之后,怎么请求新token,怎么重新发起请求,这些操作retrofit要配合rxjava来实现,后面关于rxjava我会说到。
加密解密拦截器
在这里先简单讲一下我的加密机制,主要是通过rsa+aes,也就是客户端表单提交的数据,通过aes加密,然后aes的key再通过客户端本地保存的公钥进行加密(此公钥由服务器通过rsa算法生成,打包的时候保存在客户端本地),把加密之后的key放在请求头里面,一起发送给服务器。拦截器的代码如下:
/*** @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); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
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)); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
token过期处理
这里的处理逻辑其实还蛮复杂的,看看下图(画的比较丑,不要介意)
在这里可以使用RxJava的retryWhen操作符,先看看服务器返回的数据格式:
/** * 这是服务器返回数据的一个固定格式 * @author Mr.W */public class Result<T> { public int state; public String error; public T infos;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
那么一个基本的流程是这样的:
所以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();}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
这样就可以很完美的处理了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头部处理器拦截器(这里主要处理服务器返回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()); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
总结
欢迎大家私信我交流一下,大家也可以看看下我的个人项目,关于我平时的一些文章分享到的技术,我基本都集成在上面:github地址
欢迎star哦!!
- 优雅地使用Retrofit+RxJava(二)
- 优雅地使用Retrofit+RxJava(二)
- Retrofit+RxJava在MVP模式中优雅地处理异常
- 优雅的使用MVP+RxJava+Retrofit框架
- 简单地使用下RxJava + Retrofit
- 如何优雅地使用git(二)
- Retrofit + Rxjava网络层的优雅封装
- Rxjava+Retrofit 简单使用
- Retrofit+RxJava的使用
- 使用 RxJava 和 Retrofit
- RxJava + Retrofit 结合使用
- Retrofit+Rxjava简单使用
- retrofit与rxjava使用
- Retrofit+RXJava的使用
- Retrofit结合rxjava使用
- MVP+Retrofit+RxJava 使用
- 优雅地使用SharedPreferences
- Android Retrofit 2.0(二)使用教程OkHttp3 + Gson + RxJava
- 哈弗曼树
- 理解redis高可用方案
- CodeForces 733 A.Grasshopper And the String(水~)
- 微信 js 点击下载APP
- SNMPv3的加密和认证过程
- 优雅地使用Retrofit+RxJava(二)
- 485、CAN和以太网的经验理解。
- hystrix--服务隔离与熔断框架介绍
- JDBC入门
- 微信公众号及小程序开发使用weui无效解决方法
- 动图演示:Excel 50个逆天功能,逼格秒升
- Swift工程下,合并真机和模拟器framework
- 安装netcat
- Lucene的各中文分词比较