开始本博客之前,请先阅读:
Retrofit请求数据对错误以及网络异常的处理
异常&错误
实际开发经常有这种情况,比如登录请求,接口返回的
信息包括请求返回的状态:失败还是成功,错误码,User对象等等。如果网络等原因引起的登录失败可以归结为异常,如果是用户信息输入错误导致的登录失败算是错误。
假如服务器返回的是统一数据格式:
/** * 标准数据格式 * @param <T> */public class Response<T> { public int state; public String message; public T data;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 网络异常导致的登录失败,在使用Retrofit+RxJava请求时都会直接调用subscribe的onError事件;
- 密码错误导致的登录失败,在使用Retrofit+RxJava请求时都会调用subscribe的onNext事件;
无论是异常还是错误,都要在subscribe里面处理异常信息,如下代码:
APIWrapper.getInstance().login("username", "password") .subscribe(new Observer<Response<User>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Response<User> data) { if(data.state == 1001){ //..... }else if(data.state == 1002){ } } });
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
现在我希望在发生任何错误的情况下,都会调用onError事件,并且由model来处理错误信息。那么,此时我们就应该有一个ExceptionEngine来处理事件流中的错误信息了。
在工作流中处理异常
在正常情况下,我们获取网络数据的流程通常如下:
请求接口->解析数据->更新UI
整个数据请求过程都是发生在Rx中的工作流之中。当有异常产生的时候,我们要尽量不在ui层里面进行判断,换句话说,我们没有必要去告诉ui层具体的错误信息,只需要让他弹出一个信息(Toast或者Dialog)展示我们给它的信息就行。
请求接口和数据解析都可能出错,所以在这两层进行错误处理。为了更好的解耦,我们通过拦截器拦截错误,然后根据错误类型分发信息。
拦截器
数据解析层的拦截器
这个拦截器主要是为了获取具体的错误信息,分发给上层的UI,给用户以提示,增强用户体验。
public Observable<Weather> getWeather(String cityName){ return weatherService.getWeather(cityName) //拦截服务器返回的错误 .map(new ServerResponseFunc<Weather>()) //HttpResultFunc()为拦截onError事件的拦截器,后面会讲到,这里先忽略 .onErrorResumeNext(new HttpResponseFunc<Weather>()); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
//拦截固定格式的公共数据类型Response<T>,判断里面的状态码private class ServerResponseFunc<T> implements Func1<Response<T>, T> { @Override public T call(Response<T> reponse) { //对返回码进行判断,如果不是0,则证明服务器端返回错误信息了,便根据跟服务器约定好的错误码去解析异常 if (reponse.state != 0) { //如果服务器端有错误信息返回,那么抛出异常,让下面的方法去捕获异常做统一处理 throw new ServerException(reponse.state,reponse.message); } //服务器请求数据成功,返回里面的数据实体 return reponse.data; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
所以整个逻辑是这样的:
所以在前三步的过程中,只要发生异常(服务器返回的错误也抛出了)都会抛出,这时候就触发了RxJava的OnError事件。
处理onError事件的拦截器
这个拦截器主要是将异常信息转化为用户”能看懂”的友好提示。
private class HttpResponseFunc<T> implements Func1<Throwable, Observable<T>> { @Override public Observable<T> call(Throwable throwable) { //ExceptionEngine为处理异常的驱动器 return Observable.error(ExceptionEngine.handleException(throwable)); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
两个拦截器以前使用,代码如下:
public Observable<Weather> getWeather(String cityName){ return weatherService.getWeather(cityName) //拦截服务器返回的错误 .map(new ServerResponseFunc<Weather>()) //HttpResponseFunc()为拦截onError事件的拦截器 .onErrorResumeNext(new HttpResponseFunc<Weather>()); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
调用:
APIWrapper.getInstance().getWeather("北京") .subscribe(new SampleProgressObserver<Weather>(MainActivity.this) { @Override public void onNext(WeatherBean weatherBean) { tv.setText(weatherBean.toString()); } });
- 1
- 2
- 3
- 4
- 5
- 6
- 7
相关类:
public class RxSubscriber<T> extends ErrorSubscriber<T> { @Override public void onStart() { super.onStart(); DialogHelper.showProgressDlg(context, "正在加载数据"); } @Override public void onCompleted() { DialogHelper.stopProgressDlg(); } @Override protected void onError(ApiException ex) { DialogHelper.stopProgressDlg(); Toast.makeText(context, ex.message, Toast.LENGTH_SHORT).show(); } @Override public void onNext(T t) { }}public abstract class ErrorSubscriber<T> extends Observer<T> { @Override public void onError(Throwable e) { if(e instanceof ApiException){ onError((ApiException)e); }else{ onError(new ApiException(e,123)); } } /** * 错误回调 */ protected abstract void onError(ApiException ex);}
- 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
处理异常的驱动器
package com.sanniuben.net;import android.net.ParseException;import com.google.gson.JsonParseException;import org.json.JSONException;import java.net.ConnectException;import retrofit2.adapter.rxjava.HttpException;/** * Created by Lzx on 2016/7/11. */public class ExceptionEngine { //对应HTTP的状态码 private static final int UNAUTHORIZED = 401; private static final int FORBIDDEN = 403; private static final int NOT_FOUND = 404; private static final int REQUEST_TIMEOUT = 408; private static final int INTERNAL_SERVER_ERROR = 500; private static final int BAD_GATEWAY = 502; private static final int SERVICE_UNAVAILABLE = 503; private static final int GATEWAY_TIMEOUT = 504; public static ApiException handleException(Throwable e){ ApiException ex; if (e instanceof HttpException){ //HTTP错误 HttpException httpException = (HttpException) e; ex = new ApiException(e, ERROR.HTTP_ERROR); switch(httpException.code()){ case UNAUTHORIZED: case FORBIDDEN: case NOT_FOUND: case REQUEST_TIMEOUT: case GATEWAY_TIMEOUT: case INTERNAL_SERVER_ERROR: case BAD_GATEWAY: case SERVICE_UNAVAILABLE: default: ex.message = "网络错误"; //均视为网络错误 break; } return ex; } else if (e instanceof ServerException){ //服务器返回的错误 ServerException resultException = (ServerException) e; ex = new ApiException(resultException, resultException.code); ex.message = resultException.message; return ex; } else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException){ ex = new ApiException(e, ERROR.PARSE_ERROR); ex.message = "解析错误"; //均视为解析错误 return ex; }else if(e instanceof ConnectException){ ex = new ApiException(e, ERROR.NETWORD_ERROR); ex.message = "连接失败"; //均视为网络错误 return ex; }else { ex = new ApiException(e, ERROR.UNKNOWN); ex.message = "未知错误"; //未知错误 return ex; } }}/** * 约定异常 */public class ERROR { /** * 未知错误 */ public static final int UNKNOWN = 1000; /** * 解析错误 */ public static final int PARSE_ERROR = 1001; /** * 网络错误 */ public static final int NETWORD_ERROR = 1002; /** * 协议出错 */ public static final int HTTP_ERROR = 1003;}
- 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
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
public class ApiException extends Exception { public int code; public String message; public ApiException(Throwable throwable, int code) { super(throwable); this.code = code; }}public class ServerException extends RuntimeException { public int code; public String message;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
DialogHelper.java
public class DialogHelper { /** * 通用Dialog * */ // 因为本类不是activity所以通过继承接口的方法获取到点击的事件 public interface OnOkClickListener { abstract void onOkClick(); } /** * Listener */ public interface OnCancelClickListener { abstract void onCancelClick(); } private static AlertDialog mDialog; public static void showDialog(Context context, String title, String content, final OnOkClickListener listenerYes, final OnCancelClickListener listenerNo) { showDialog(context, context.getString(android.R.string.ok), context.getString(android.R.string.cancel), title, content, listenerYes, listenerNo); } public static void showDialog(Context context, String ok, String cancel, String title, String content, final OnOkClickListener listenerYes, final OnCancelClickListener listenerNo) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setMessage(content); // 设置title builder.setTitle(title); // 设置确定按钮,固定用法声明一个按钮用这个setPositiveButton builder.setPositiveButton(ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // 如果确定被电击 if (listenerYes != null) { listenerYes.onOkClick(); } mDialog = null; } }); // 设置取消按钮,固定用法声明第二个按钮要用setNegativeButton builder.setNegativeButton(cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // 如果取消被点击 if (listenerNo != null) { listenerNo.onCancelClick(); } mDialog = null; } }); // 控制这个dialog可不可以按返回键,true为可以,false为不可以 builder.setCancelable(false); // 显示dialog mDialog = builder.create(); if (!mDialog.isShowing()) mDialog.show(); } public static void showDialog(Context context, int ok, int cancel, int title, int content, final OnOkClickListener listenerYes, final OnCancelClickListener listenerNo) { showDialog(context, context.getString(ok), context.getString(cancel), context.getString(title), context.getString(content), listenerYes, listenerNo); } static ProgressDialog progressDlg = null; /** * 启动进度条 * * @param strMessage 进度条显示的信息 * @param // 当前的activity */ public static void showProgressDlg(Context ctx, String strMessage) { if (null == progressDlg) { if (ctx == null) return; progressDlg = new ProgressDialog(ctx); //设置进度条样式 progressDlg.setProgressStyle(ProgressDialog.STYLE_SPINNER); //提示的消息 progressDlg.setMessage(strMessage); progressDlg.setIndeterminate(false); progressDlg.setCancelable(true); progressDlg.show(); } } public static void showProgressDlg(Context ctx) { showProgressDlg(ctx, ""); } /** * 结束进度条 */ public static void stopProgressDlg() { if (null != progressDlg && progressDlg.isShowing()) { progressDlg.dismiss(); progressDlg = null; } if (null != dialog && dialog.isShowing()) { dialog.dismiss(); dialog = null; } } private static Dialog dialog; public static void showDialogForLoading(Context context, String msg, boolean cancelable) { if (null == dialog) { if (null == context) return; View view = LayoutInflater.from(context).inflate(R.layout.layout_loading_dialog, null); TextView loadingText = (TextView)view.findViewById(R.id.loading_tip_text); loadingText.setText(msg); dialog = new Dialog(context, R.style.loading_dialog_style); dialog.setCancelable(cancelable); dialog.setCanceledOnTouchOutside(cancelable); dialog.setContentView(view, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)); Activity activity = (Activity) context; if (activity.isFinishing()) return; dialog.show(); } }}
- 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
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
可能本博客也不是最好的解决方案,如果有更好的想法,我愿与你互相交流!
代码参考:https://github.com/122627018/Retorfit_RxJava_Exception
看到bobo_wang的文章,不仅感觉有受益匪浅,这里做下介绍。
首先定义如下Transformer转换器。
public static <T> Observable.Transformer<Response<T>, T> sTransformer() { return responseObservable -> responseObservable.map(tResponse -> { if (!tResponse.success) throw new RuntimeException(tResponse.code); return tResponse.data; }).onErrorResumeNext(new HttpResponseFunc<>()); } public static <T> Observable.Transformer<T, T> switchSchedulers() { return observable -> observable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } private static class HttpResponseFunc<T> implements Func1<Throwable, Observable<T>> { @Override public Observable<T> call(Throwable throwable) { //ExceptionEngine为处理异常的驱动器 return Observable.error(new Throwable(throwable)); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
调用:
public void login(View v){ apiservice.login(name,pwd) .compose(Transformers.sTransformer()) .compose(Transformers.switchSchedulers()) .subscribe(subscriber);}private Subscriber<UserModel> subscriber = new Subscriber<UserModel>() { @Override public void onCompleted() { // do onCompleted } @Override public void onError(Throwable e) { // do on success != true; // do on http error // do on other error } @Override public void onNext(UserModel model) { // parse data } };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
接口:
@FormUrlEncoded @POST("interface?login")Observable<Response<UserModel>> login(@Field("name") String name,@Field("pwd") String pwd);
- 1
- 2
最后再来点干货。
Transformer 和 Func 处理的区别
如上的处理,定义了 一个 sTransformer 和一个 HttpResponseFunc,
从中可以明显感觉的到sTransformer其实也是可以用Func1来定义的,
public void login(View v){ apiservice.login(name,pwd) .compose(Transformers.switchSchedulers()) .map(new TransFuc<UserModel>()) .onErrorReturn(new HttpResponseFunc<>()) .subscribe(subscriber);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
public static class TransFuc<T> implements Func1<Response<T>, T> { @Override public T call(Response<T> tResponse) { if (!tResponse.success) throw new RuntimeException(tResponse.code); return tResponse.data; } }
- 1
- 2
- 3
- 4
- 5
- 6
Transformer作用于整个流,Func1是一个操作符,作用于数据项。
不规范数据的处理
有时候服务器返回的数据并不是十分规范的,比如
正常返回是这样的
{ "success": true, // 是否成功 "status": "1", // 状态码 "data": { // 内容 } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
错误时时这样的
{ "success": false, // 是否成功 "status": "0", // 状态码 "data": "371" //错误码 }
- 1
- 2
- 3
- 4
- 5
这时候如果我么用泛型处理
public class Response<T> { public boolean success; public String status; public T data;}
- 1
- 2
- 3
- 4
- 5
针对这种数据,我们的泛型该怎么写成了问题,错误的时候是String,正确的时候是Bean?
如果我们直接写成JavaBean,那么我们会得到一个错误,LinkedTreeMap cannot be cast to xxx
类型转换错误.这时候只能将泛型写成String来处理了,使用如下Transformer
public static Observable.Transformer<String, UserModel> trans() { return stringObservable -> stringObservable.map(s -> { Response parseJson = GsonUtil.parseJson(s, Response.class); if (null == parseJson) { throw new RuntimeException("null == parseJson"); } if (PatternsUtil.isNum(parseJson.data.toString())) { throw new RuntimeException(parseJson.data.toString()); } return GsonUtil.parseJson(s, UserModel.class); }).onErrorResumeNext(new HttpResponseFunc<>()); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
使用就变成了如下这样
public void login(View v){ apiservice.login(name,pwd) .compose(Transformers.switchSchedulers()) .compose(Transformers.trans()) .subscribe(subscriber);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
封装的越来越简介了,用到实际项目吧!
再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.youkuaiyun.com/jiangjunshow