1 .背景
做过app开发的都知道,一般公认的接口数据格式如下
{
"status": 200,
"data": {
"sex": "男",
"userId": 123456,
"userName": "张三"
},
"msg": "登录成功"
}
当登录错误的时候,返回的数据格式如下:
{
"status": 400,
"data": {},//或者null
"msg": "账号不存在"
}
Android开发人员一般会在项目框架中统一处理解析后台返回的数据,而不需要每个接口手动解析数据了.比如,我们用Retrofit框架,请求接口时候,定义如下:
//登录
@POST("login")
Observable> toLogin(@Body RequestBody body);
定义全局统一的接收数据的javaBean
public class Response {
private int status; //状态码 0:失败 1:成功
private String msg; // 显示的信息
private T messageList; // 业务数据
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMsg() {
return msg == null ? "未知原因" : msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getResults() {
return messageList;
}
public void setResults(T results) {
this.messageList = results;
}
@Override
public String toString() {
return "Response{" +
"status=" + status +
", msg='" + msg + '\'' +
", results=" + messageList.toString() +
'}';
}
}
全局统一处理网络请求,根据状态码区分业务.
前面说了一堆正常操作,现在问题来了.....当登录正确的时候,后台返回的数据是正常的,但是失败的时候,返回的数据如下
{
"messageList": [
{
"exceptionClass": "com.zdcx.base.common.exception.AppException",
"messageBody": "用户已存在",
"messageForDeveloper": "MST00002",
"messageId": "MST00002",
"messageInstanceId": "3CVHZF33WFCC7KAFL5JZCLoHUY",
"messageLevel": "ERROR",
"messageSubject": "用户已存在",
"path": "/api/cust/sms"
}
],
"status": 400
}
什么? messageList在正确的时候是个对象,在失败的时候,确是个数组???你让我怎么接??于是乎,找后台,让他们修改为统一的格式...遇到后台好还好,不好的,比如我们的,一句话: 框架就是这样封装的,我改不了.....我内心一万头草泥马呼啸而过....好吧,你不解决,我自己解决吧.....解决方案如下
2.方案一
retrofit接口中统一用Object接收,这儿在框架中可以统一处理错误的情况,但是status=200的时候,就得自己手动解析成对应的javaBean了..
//登录
@POST("login")
Observable> toLogin(@Body RequestBody body);
但是这样还得在回调中每次手动解析javaBean,也很麻烦啊...能不能像标准格式一样,我只关心正确的业务数据,回调过去直接是解析好的JavaBean呢?于是乎,方案二出来了
3.方案二: 使用OkHttp中的Interceptor
通过拦截器,拦截后台返回的数据,然后我们只需要判断status,如果200,则直接返回response,如果不是,则抛出异常,此时异常会回调在OnError(本人项目是Rxjava+retrofit)中.拦截器代码如下:
public abstract class ResponseBodyInterceptor implements Interceptor {
@NotNull
@Override
public Response intercept(@NotNull Chain chain) throws IOException {
Request request = chain.request();
String url = request.url().toString();
Response response = chain.proceed(request);
ResponseBody responseBody = response.body();
if (responseBody != null) {
long contentLength = responseBody.contentLength();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.getBuffer();
if ("gzip".equals(response.headers().get("Content-Encoding"))) {
GzipSource gzippedResponseBody = new GzipSource(buffer.clone());
buffer = new Buffer();
buffer.writeAll(gzippedResponseBody);
}
MediaType contentType = responseBody.contentType();
Charset charset;
if (contentType == null || contentType.charset(StandardCharsets.UTF_8) == null) {
charset = StandardCharsets.UTF_8;
} else {
charset = contentType.charset(StandardCharsets.UTF_8);
}
if (charset != null && contentLength != 0L) {
return intercept(response,url, buffer.clone().readString(charset));
}
}
return response;
}
abstract Response intercept(@NotNull Response response,String url, String body);
}
我们只需要继承该拦截器,然后处理自己的业务逻辑就行了,示例如下:
/**
* Created by admin
* Created Time: 2020/3/5 16:22
* Description: 自己解析错误信息,并构造成标准json格式 body就是后台返回的json
* PS: 怎么解析根据自己业务来,我是解析我后台给我的错误数据....
*/
public class HandleErrorInterceptor extends ResponseBodyInterceptor {
@Override
Response intercept(@NotNull Response response, String url, String body) {
try {
JSONObject jsonObject = new JSONObject(body);
int status = jsonObject.optInt("status");
if (status != 200) {
String errorMsg = jsonObject.getJSONArray("messageList").getJSONObject(0).getString("messageBody");
throw new MyException(status, errorMsg);
}
} catch (JSONException e) {
e.printStackTrace();
}
return response;
}
}
MyException的代码如下
public class MyException extends RuntimeException {
private int httpCode;
private String errMsg;
public int getHttpCode() {
return httpCode;
}
public String getErrMsg() {
return errMsg;
}
public MyException(int httpCode, String message) {
super(message);
this.httpCode = httpCode;
this.errMsg = message;
}
}
为啥继承RuntimeException 而不是HTTPException或者直接Exception呢? 其实我本来是想继承HTTPException的,但是发现编译器直接报错....这就需要了解RuntimeException 和Exception的区别了,不懂的同学可以查一下...最终OkHttp抛出的该异常,会回调在OnError中,代码如下:
/**
* 統一的网络请求
*
* @param observable 被观察者
* @param 网络返回的数据
* @param compositeDisposable 用于取消网络请求
*/
public void request(Observable> observable,
final CompositeDisposable compositeDisposable, final BaseView view,
final CallBackListener listener) {
if (observable == null || compositeDisposable == null || view == null || listener == null) {
return;
}
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer>() {
@Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
@Override
public void onNext(Response response) {
//回调成功,业务逻辑省略....
}
@Override
public void onError(Throwable e) {
// 此处会回调刚才我们自定义MyException.....
if (e instanceof MyException) {
listener.onError(((MyException) e).getErrMsg());
if (((MyException) e).getHttpCode() == 403) {
toLogin();
}
return;
}
listener.onError(e.getMessage());
}
@Override
public void onComplete() {
}
});
}
到此,问题就解决了....此外,自定义拦截器中包含有请求的url,我们可以根据url来定向的修改某个接口的数据啊,从此彻底摆脱与后台的各种撕逼吧...永远不要跟有些人争吵,这样只会拉低你的智商....学会自己动手解决各种问题吧.