美文网首页Android开发UI
Android使用Retrofit修改后台返回的不规范json数

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

作者: small_tadpole | 来源:发表于2020-03-06 10:16 被阅读0次

    1 .背景

    做过app开发的都知道,一般公认的接口数据格式如下

    {
        "status": 200,
        "data": {
            "sex": "男",
            "userId": 123456,
            "userName": "张三"
        },
        "msg": "登录成功"
    }
    

    当登录错误的时候,返回的数据格式如下:

    {
        "status": 400,
        "data": {},//或者null
        "msg": "账号不存在"
    }
    

    Android开发人员一般会在项目框架中统一处理解析后台返回的数据,而不需要每个接口手动解析数据了.比如,我们用Retrofit框架,请求接口时候,定义如下:

     //登录
        @POST("login")
        Observable<Response<LoginBean>> toLogin(@Body RequestBody body);
    

    定义全局统一的接收数据的javaBean

    public class Response<T> {
    
        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<Response<Object>> 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 <T>                 网络返回的数据
        * @param compositeDisposable 用于取消网络请求
        */
       public <T, K extends BasePresenter> void request(Observable<Response<T>> observable,
                                                        final CompositeDisposable compositeDisposable, final BaseView<K> view,
                                                        final CallBackListener<T> listener) {
    
           if (observable == null || compositeDisposable == null || view == null || listener == null) {
               return;
           }
           observable.subscribeOn(Schedulers.io())
                   .observeOn(AndroidSchedulers.mainThread())
                   .subscribe(new Observer<Response<T>>() {
                       @Override
                       public void onSubscribe(Disposable d) {
                           compositeDisposable.add(d);
                       }
    
                       @Override
                       public void onNext(Response<T> 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数

        本文链接:https://www.haomeiwen.com/subject/whfvrhtx.html