博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
错误码全局处理(一)
阅读量:6388 次
发布时间:2019-06-23

本文共 8949 字,大约阅读时间需要 29 分钟。

一前言

联网框架已经是Rxjava和Retrofit的天下。但是错误码的统一封装目前参差不齐。笔者将通过这篇文章自诉怕坑历程。在此首先感谢的指点。

二开始爬坑

每个app都有自定义的API错误,比如token失效错误,参数错误等。一般后台会给我们返回一个错误的状态码。如下json(为了讲述方便,我们规定0表示正确,其余错误码都是表示不同的错误)

{
"data":"","error_code":8,"msg":"请重新登录"}复制代码
{
"data":null,"error_code":8,"msg":"请重新登录"}复制代码
这两个Json对于我们Android开发来说,可是有很大的不一样。我们知道Retrofit可以添加解析器
.addConverterFactory(GsonConverterFactory.create())//可以添加自定义解析器和默认的解析器复制代码
如果你们后台返回第一种,那么只要是error_code错误,我们的请求都会报数据解析异常,原因:内置的解析器解析的是对象,结果你们后台返回的却是字符串。(这种是因为后台不规范照成的)

这个时候你有两种解决方式:

  • 自己自定义解析器,自己抛异常
  • 让你们后台改成第二种返回的结果

自定义解析器方式

这种方式的原理是:在ResponseBodyConverter 类中获取到返回的结果是一个字符串,我们要在原来的解析之前,先去解析一遍,这次解析
只去解析error_code 和msg字段,只要是error_code不是0,我们就自己抛出来一个异常。这样请求层中只要是错误,不管是API错误,还是联网错误都会走到OnError回调中,这样我们就可以统一处理所有的错误,Toast告诉用户是什么错误。自定义解析器的另外一个好处就是当请求到的数据需要解密的时候,自定义解析器简直的完美应对。由于篇幅限制,自定义解析器自行google。

让你们后台改成第二种返回的结果

后台改完这种结果之后,这时候我们的错误会出现在两个地方:onNext回调中和onError回调中。

联网正确,解析正确,只是单纯的API错误,当然会走到OnNext中。

联网不正确,一定会走onError回调。

这里我们就要想办法把OnNext中关于API错误的回调走到onError中,并且能统一封装起来。这就需要介绍两个操作符:flatMap +compose去解决这个问题。

那么怎么使用呢?我们通过代码去讲解:

Observable.create(new ObservableOnSubscribe
() { @Override public void subscribe(ObservableEmitter
emitter) throws Exception { emitter.onNext(1); }}).subscribe(new Observer
() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(Integer integer) { } @Override public void onError(Throwable e) { } @Override public void onComplete() { }});复制代码

这段代码运行起来,默认是走onNext回调,现在我们要让他走OnError回调。我们需要去定义一个静态方法

public static 
ObservableTransformer
APIError() { return upstream -> upstream.flatMap(it -> { if (it.equals(1)) { return Observable.error(new RuntimeException("11")); } else { return Observable.just(it); } });}复制代码

然后在上边代码通过compose添加上这个静态方法:

compose(ErrorUtils.APIError())复制代码

我们再去运行:发现走到了onError回调。我们通过改变流的整体走向,完成了所有的错误都会在onError中去处理。

上边的代码逻辑需要根据实际的业务去做处理,其本质不变,这里只是给读者提供一个思路。

三自动刷新token

由于业务需求变化,增加了自动刷新token,即使token过期,要求去请求token最新的token,之后再用新的token去请求上次因为token过期请求错误的接口,并且这一过程对于用户来说是无感的。

分析需求:任何接口都有可能token过期,这就要求能统一封装起来。这里笔者提供两种思路:

  • 使用动态代理+retryWhen操作符
  • 只使用Rxhava操作符:retryWhen+onErrorResumeNext
使用动态代理+retryWhen操作符

动态代理本质就是动态的去扩展方法中的逻辑,而且没有耦合性。这里我们要扩展的方法是什么?

扩展Retrofit对象Creat的所有方法

T t = mRetrofit.create(tClass);复制代码

然后传递到动态代理类里边,如下:

public 
T getProxy(Class
tClass) { T t = mRetrofit.create(tClass); return (T) Proxy.newProxyInstance(tClass.getClassLoader(), new Class
[] { tClass }, new ProxyHandler(t));}复制代码

对应的ProxyHandler类是实现InvocationHandler接口的类(这是动态代理的写法,看不懂就去google一下动态代理入门)

public class ProxyHandler implements InvocationHandler {    private Object mProxyObject;    public ProxyHandler(Object proxyObject) {        mProxyObject = proxyObject;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) {        try {            return method.invoke(mProxyObject, args);        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        }        return null;    }}复制代码

invoke方法里边就是通过反射调用原本的方法。我们只要在他之后去写这些代码逻辑即可。

上边代码修改成这样:

@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    return Observable.just(1).flatMap(o -> {        try {            return (Observable
) method.invoke(mProxyObject, args); } catch (InvocationTargetException e) { e.printStackTrace(); } return null; });}复制代码

接着就是丰富他的逻辑,让他可以重试,这就需要介绍rxhava中的一个操作符:retryWhen ,当发生错误的时候异常就会首先触发这个方法执行,而它的返回值决定了是否需要继续重复上次请求。

关于retryWhen这里需要说明一下:如果返回流发送onNext事件,则触发重订阅。如果不是,那么就会把这个错误传递给上层的onError方法

我们只需要在它之前加上我们特殊的逻辑,就可以让他再次订阅。

更多关于它的说明请

现在就去添加逻辑

public class ProxyHandler implements InvocationHandler {    private Object mProxyObject;    public ProxyHandler(Object proxyObject) {        mProxyObject = proxyObject;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) {        return Observable.just(1).flatMap(o -> {                return (Observable
) method.invoke(mProxyObject, args); }).retryWhen(new Function
, ObservableSource
>() { @Override public ObservableSource
apply(Observable
throwableObservable) { //这里return决定他是否继续订阅 return throwableObservable.flatMap(new Function
>() { @Override public ObservableSource
apply(Throwable throwable) throws Exception { //判断是不是token失效,这里假如token等于8失效 if (throwable instanceof ApiException) { if (((ApiException) throwable).getErrorCode() == 8) { //上边return的是这里的return,这里去请求token,如果请求成功就去创建一个可以重复订阅的 // 如果刷新token的请求也错误,他会直接return一个错误也就不会发生再次订阅,错误继续传递下去 //这里你可能会问为什么网络请求不去切换线程,你可以打印一下,他本身就是子线程去创建的流,所以不用切换线程。 return RetrofitUtil. getInstance() .create(API.class) .Login("wangyong", "111111") .flatMap(loginBean -> { SPUtils.saveString("token", loginBean.getData().getToken()); //这里创建一个新流去return,保证了先去请求token,之后再去重复订阅 return Observable.just(1); }); } } //如果不是token错误,会创建一个新的流,把错误传递下去 return Observable.error(throwable); } }); } }); }复制代码

上边的注释解释的很清楚,只要认真读,应该都能看明白。细心的朋友可能发现:请求token的接口没有订阅者照样可以发起网络请求,视乎和 Retrofit没有订阅者不会发起请求产生冲突,其实,他并不是没有订阅者,这个请求的过程是在流转换过程中发生的,外部请求过程中已经发生了订阅,所以这里能发起请求。

最后就是如何使用:

这里是无感更新token的使用方式:

RetrofitUtil.getInstance().getProxy(API.class)复制代码

这里是不去更新token的使用方式:

RetrofitUtil .getInstance().create(API.class)复制代码

这种实现方式会有一个问题,那就是并发请求时候会出现多次请求Token刷新接口。如果你的刷新token接口在token有效期内返回还是原来的token,那么请求并发几次请求几次,如果每次请求刷新token接口后台都给你一个新的token而不管token是否过期,那么请求刷新token的接口的次数会更多。原因如下图:

关于并发问题给服务器带来额外的压力。我们稍后在谈论怎么解决。我们先去看怎么通过第二种方式去解决这个动态刷新token。

只使用Rxhava操作符:retryWhen+onErrorResumeNext

这种方法和开始讲解改变流的走向的思路是一样的。整体代码如下:

public static 
ObservableTransformer
specialErrorHandler() { return upstream -> upstream .onErrorResumeNext(new Function
>() { @Override public ObservableSource
apply(Throwable throwable) throws Exception { if (throwable instanceof ApiException && ((ApiException) throwable).getErrorCode() == 8) { //这里去请求,然后再确定返回值 return RetrofitUtil. getInstance() .create(API.class) .Login("wangyong", "111111") .flatMap(loginBean -> { SPUtils.saveString("token", loginBean.getData().getToken()); //这里创建一个新流去return,保证了先去请求token,之后再去重复订阅 return Observable.error(new ApiException(-999, "这表示特殊错误,表示要重复去请求")); }); } else { //如果不是token错误,会创建一个新的流,把错误传递下去 return Observable.error(throwable); } } }) .retryWhen(new Function
, ObservableSource
>() { @Override public ObservableSource
apply(Observable
throwableObservable) throws Exception { return throwableObservable.flatMap(new Function
>() { @Override public ObservableSource
apply(Throwable throwable) throws Exception { if (throwable instanceof ApiException && ((ApiException) throwable).getErrorCode() == -999) { return Observable.just(1); } else { //如果不是token错误,会创建一个新的流,把错误传递下去 return Observable.error(throwable); } } }); } });复制代码

需要解释的是onErrorResumeNext,他会在发生错误的第一时间拿到错误类型,紧接着会把错误类型再次传递给retryWhen,我们可以在retryWhen里边通过不同的错误,去处理到底是重复请求还是直接把错误扔出去。

当然这种实现方式也会带来并发请求多次刷新token的问题,我们先放一放这个问题。我们先来对比一下这两种实现方式的灵活度。

假如需求再次变化要求不去自动刷新token,而是去跳转登录界面,登录完成之后,继续请求未登录之前的接口。这个需求都是需要上下文对象,很明显第二种实现方式会更加灵活,扩展性更好。

下一篇文章笔者去实现上边的两种需求和解决并发问题。

转载地址:http://jvdha.baihongyu.com/

你可能感兴趣的文章
centos6.2安装tomcat
查看>>
利用ansible实现一键化部署 rsync服务
查看>>
nginx根据条件跳转+跳转规则
查看>>
(转载)Javascript异步编程的4种方法
查看>>
ACM suvey
查看>>
Oracle的case 用法
查看>>
Python之路【第二十七篇】:反射
查看>>
敌兵布阵
查看>>
Web.config详解 [转]
查看>>
PHP杂记
查看>>
面试题整理10
查看>>
POP跳转页面,从3号跳回1号,
查看>>
[Android] keytools生成jsk文件以及获取sha1码
查看>>
一道算法题
查看>>
qt.network.ssl: QSslSocket: cannot call unresolved function SSLv23_client_method
查看>>
WM-结汇
查看>>
概述--Nginx集成Vcenter 6.X HTML Console系列之 1--(共4)
查看>>
mysql查询重复
查看>>
ORACLE触发器的管理与实际应用【weber出品】
查看>>
C# SQLite
查看>>