Android使用Retrofit修改后台返回的不规范json数据

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来定向的修改某个接口的数据啊,从此彻底摆脱与后台的各种撕逼吧...永远不要跟有些人争吵,这样只会拉低你的智商....学会自己动手解决各种问题吧.

你可能感兴趣的:(Android使用Retrofit修改后台返回的不规范json数据)