• Retrofit全攻略——进阶篇


    最近事比较多,距离上次写文章已经过去了一个月了。上一篇文章Retrofit全攻略——基础篇 介绍了Retrofit的基础用法,这篇文章介绍点进阶的用法。

    打印网络日志

    在开发阶段,为了方便调试,我们需要查看网络日志。因为Retrofit2.0+底层是采用的OKHttp请求的。可以给OKHttp设置拦截器,用来打印日志。
    首先可以在app/build.gradle中添加依赖,这是官方的日志拦截器。

    compile 'com.squareup.okhttp3:logging-interceptor:3.3.0'
    

    然后在代码中设置:

        public static Retrofit getRetrofit() {
            //如果mRetrofit为空  或者服务器地址改变 重新创建
            if (mRetrofit == null) {
                OkHttpClient httpClient;
                OkHttpClient.Builder builder=new OkHttpClient.Builder();
                
                //阶段分为开发和发布阶段,当前为开发阶段设置拦截器
                if (BuildConfig.DEBUG) {
                    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
                    //设置拦截器级别
                    logging.setLevel(HttpLoggingInterceptor.Level.BODY);
                    builder.addInterceptor(logging);
                }
                httpClient=builder.build();
                //构建Retrofit
                mRetrofit = new Retrofit.Builder()
                        //配置服务器路径
                        .baseUrl(mServerUrl)
                        //返回的数据通过Gson解析
                        .addConverterFactory(GsonConverterFactory.create())
                        //配置回调库,采用RxJava
                        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                        //设置OKHttp模板
                        .client(httpClient)
                        .build();
            }
            return mRetrofit;
        }
    

    当处于开发阶段的时候,设置监听日志的拦截器。拦截有4个级别,分别是

    1. BODY
    2. HEADERS
    3. BASIC
    4. NONE

    其中BODY输出的日志是最全的。

    添加相同的请求参数

    为了更好的管理迭代版本,一般每次发起请求的时候都传输当前程序的版本号到服务器。
    有些项目我们每次还会传用户id,token令牌等相同的参数。
    如果在每个请求的接口都添加这些参数太繁琐。Retrofit可以通过拦截器添加相同的请求参数,无需再每个接口添加了。

    步骤一,自己拦截器

    public class CommonInterceptor implements Interceptor {
        @Override
        public Response intercept(Interceptor.Chain chain) throws IOException {
            Request oldRequest = chain.request();
    
            // 添加新的参数
            HttpUrl.Builder authorizedUrlBuilder = oldRequest.url()
                    .newBuilder()
                    .scheme(oldRequest.url().scheme())
                    .host(oldRequest.url().host())
                    .addQueryParameter("device_type", "1")
                    .addQueryParameter("version", BuildConfig.VERSION_NAME)
                    .addQueryParameter("token", PreUtils.getString(R.string.token))
                    .addQueryParameter("userid", PreUtils.getString(R.string.user_id));
    
            // 新的请求
            Request newRequest = oldRequest.newBuilder()
                    .method(oldRequest.method(), oldRequest.body())
                    .url(authorizedUrlBuilder.build())
                    .build();
    
            return chain.proceed(newRequest);
        }
    }
    
    

    实现原理就是拦截之前的请求,添加完参数,再传递新的请求。这个位置我添加了四个公共的参数。
    然后再Retrofit初始化的时候配置。

            if (mRetrofit == null) {
                OkHttpClient httpClient;
                OkHttpClient.Builder builder=new OkHttpClient.Builder();
                //添加公共参数
                builder.addInterceptor(new CommonInterceptor());
                httpClient=builder.build();
                //构建Retrofit
                mRetrofit = new Retrofit.Builder()
                        //....
                        .client(httpClient)
                        .build();
            }
    

    处理约定错误

    除了常见的404,500等异常,网络请求中我们往往还会约定些异常,比如token失效,账号异常等等。

    以token失效为例,每次请求我们都需要验证是否失效,如果在每个接口都处理一遍错误就有点太繁琐了。

    我们可以统一处理下错误。

    步骤一,Retrofit初始化时添加自定义转化器

    mRetrofit = new Retrofit.Builder()
            //配置服务器路径
          baseUrl(mServerUrl)
          //配置回调库,采用RxJava
         .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
         //配置转化库,默认是Gson,这里修改了。
         .addConverterFactory(ResponseConverterFactory.create())
         .client(httpClient)
         .build();
    

    步骤二 创建ResponseConverterFactory

    步骤一 ResponseConverterFactory这个类是需要我们自己创建的。

    public class ResponseConverterFactory extends Converter.Factory {
        /**
         * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
         * decoding from JSON (when no charset is specified by a header) will use UTF-8.
         */
        public static ResponseConverterFactory create() {
            return create(new Gson());
        }
    
        /**
         * Create an instance using {@code gson} for conversion. Encoding to JSON and
         * decoding from JSON (when no charset is specified by a header) will use UTF-8.
         */
        public static ResponseConverterFactory create(Gson gson) {
            return new ResponseConverterFactory(gson);
        }
    
        private final Gson gson;
    
        private ResponseConverterFactory(Gson gson) {
            if (gson == null) throw new NullPointerException("gson == null");
            this.gson = gson;
        }
    
        @Override
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                                Retrofit retrofit) {
          //  TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
            return new GsonResponseBodyConverter<>(gson, type);
        }
    
        @Override
        public Converter<?, RequestBody> requestBodyConverter(Type type,
                                                              Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
            TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
            return new GsonRequestBodyConverter<>(gson, adapter);
        }
    }
    
    

    这里面我们自定义了请求和响应时解析JSON的转换器——GsonRequestBodyConverterGsonResponseBodyConverter

    其中GsonRequestBodyConverter 负责处理请求时传递JSON对象的格式,不需要额外处理任何事,直接使用默认的GSON解析。代码我直接贴出来:

    final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
        private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
        private static final Charset UTF_8 = Charset.forName("UTF-8");
    
        private final Gson gson;
        private final TypeAdapter<T> adapter;
    
        GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
            this.gson = gson;
            this.adapter = adapter;
        }
    
        @Override public RequestBody convert(T value) throws IOException {
            Buffer buffer = new Buffer();
            Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
            JsonWriter jsonWriter = gson.newJsonWriter(writer);
            adapter.write(jsonWriter, value);
            jsonWriter.close();
            return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
        }
    }
    

    GsonResponseBodyConverter负责把响应的数据转换成JSON格式,这个我们需要处理一下。

    
    public class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
        private final Gson gson;
        private final Type type;
    
        GsonResponseBodyConverter(Gson gson, Type type) {
            this.gson = gson;
            this.type = type;
        }
    
        @Override
        public T convert(ResponseBody value) throws IOException {
            String response = value.string();
            try {
                Log.i("YLlibrary", "response>>> "+response);
                //ResultResponse 只解析result字段
                BaseInfo baseInfo = gson.fromJson(response, BaseInfo.class);
                if (baseInfo.getHeader().getCode().equals("1")) {
                    //正确
                    return gson.fromJson(response, type);
                } else {
                    //ErrResponse 将msg解析为异常消息文本 错误码可以自己指定.
                    throw new ResultException(-1024, baseInfo,response);
                }
            } finally {
            }
        }
    
    }
    
    

    这种情况只是应用于后台接口数据统一的情况。比如我们项目的格式是这样的

          {
              header : {"message":"token失效","code":"99"}
              data : {}
          }
    

    当code值是1的时候,表示正确,其它数字表示错误。只有正确的时候data才会有内容。

    这里我用BaseInfo解析这个JSON:

    public class BaseInfo {
    
        /**
         * header : {"message":"用户名或密码错误","code":"0"}
         * data : {}
         */
    
        private HeaderBean header;
    
        public HeaderBean getHeader() {
            return header;
        }
    
        public void setHeader(HeaderBean header) {
            this.header = header;
        }
    
    
        public static class HeaderBean {
            /**
             * message : 用户名或密码错误
             * code : 0
             */
    
            private String message;
            private String code;
    
            public String getMessage() {
                return message;
            }
    
            public void setMessage(String message) {
                this.message = message;
            }
    
            public String getCode() {
                return code;
            }
    
            public void setCode(String code) {
                this.code = code;
            }
        }
    }
    

    服务器返回的数据实体对象全部继承BaseInfo 只是data内容不一样。

    ResultException这个类用于捕获服务器约定的错误类型

    /**
     * 这个类用于捕获服务器约定的错误类型
     */
    public class ResultException extends RuntimeException {
    
        private int errCode = 0;
        private BaseInfo info;
        private String response;
        public ResultException(int errCode, BaseInfo info,String response) {
            super(info.getHeader().getMessage());
            this.info=info;
            this.errCode = errCode;
            this.response=response;
        }
    
        public String getResponse() {
            return response;
        }
    
        public void setResponse(String response) {
            this.response = response;
        }
    
        public int getErrCode() {
            return errCode;
        }
    
        public BaseInfo getBaseInfo(){
            return info;
        }
    }
    

    最后定义Retrofit处理异常的代码

    public abstract class AbsAPICallback<T> extends Subscriber<T> {
    
        //对应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;
        //出错提示
        private final String networkMsg;
        private final String parseMsg;
        private final String unknownMsg;
    
        protected AbsAPICallback(String networkMsg, String parseMsg, String unknownMsg) {
            this.networkMsg = networkMsg;
            this.parseMsg = parseMsg;
            this.unknownMsg = unknownMsg;
        }
        public  AbsAPICallback(){
            networkMsg="net error(联网失败)";
            parseMsg="json parser error(JSON解析失败)";
            unknownMsg="unknown error(未知错误)";
        }
        ProgressBar progressBar;
        public  AbsAPICallback(ProgressBar progressBar){
            this();
            this.progressBar=progressBar;
        }
        @Override
        public void onError(Throwable e) {
            Throwable throwable = e;
            //获取最根源的异常
            while(throwable.getCause() != null){
                e = throwable;
                throwable = throwable.getCause();
            }
    
            ApiException ex;
            if (e instanceof HttpException){             //HTTP错误
                HttpException httpException = (HttpException) e;
                ex = new ApiException(e, httpException.code());
                switch(httpException.code()){
                    case UNAUTHORIZED:
                    case FORBIDDEN:
                      //  onPermissionError(ex);          //权限错误,需要实现
                       // break;
                    case NOT_FOUND:
                    case REQUEST_TIMEOUT:
                    case GATEWAY_TIMEOUT:
                    case INTERNAL_SERVER_ERROR:
                    case BAD_GATEWAY:
                    case SERVICE_UNAVAILABLE:
                    default:
                        ex.setDisplayMessage(networkMsg);  //均视为网络错误
                        onNetError(ex);
                        break;
                }
            } else if (e instanceof ResultException){    //服务器返回的错误
                ResultException resultException = (ResultException) e;
                onResultError(resultException);
            } else if (e instanceof JsonParseException
                    || e instanceof JSONException
                    || e instanceof ParseException){
                ex = new ApiException(e, ApiException.PARSE_ERROR);
                ex.setDisplayMessage(parseMsg);            //均视为解析错误
                onNetError(ex);
            } else {
                ex = new ApiException(e, ApiException.UNKNOWN);
                ex.setDisplayMessage(unknownMsg);          //未知错误
                onNetError(ex);
            }
        }
        static long time;
        protected  void onNetError(ApiException e){
            long currentTime=System.currentTimeMillis();
            if(currentTime-time>3000){  //防止连续反馈
                time=currentTime;
                UIUtils.showToast("网络加载失败");
            }
            e.printStackTrace();
            onApiError(e);
        }
        /**
         * 错误回调
         */
        protected  void onApiError(ApiException ex){
            Log.i("YLLibrary","onApiError");
            if(progressBar!=null)
                UIUtils.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        progressBar.setVisibility(View.GONE);
                        progressBar=null;
                    }
                });
        }
    
    //    /**
    //     * 权限错误,需要实现重新登录操作
    //     */
    //    protected void  onPermissionError(ApiException ex){
    //        ex.printStackTrace();
    //    }
    
        /**
         * 服务器返回的错误
         */
        protected  synchronized  void onResultError(ResultException ex){
    //        if(ex.getErrCode()== XApplication.API_ERROR){
    //            UIUtils.getContext().onApiError(); //可以用来处理Token失效
    //            return ;
    //        }
            if(ConstantValue.TOKEN_ERROR.equals(ex.getBaseInfo().getHeader().getCode())
                    &&!TextUtils.isEmpty(PreUtils.getString(R.string.token))){ //验证token是否为空是为了防止连续两次请求
                PreUtils.putString(R.string.user_id,null);
                PreUtils.putString(R.string.token,null);
                PreUtils.putString(R.string.orgDistrict,null);
                if(BaseActivity.runActivity!=null){
                    Intent intent = new Intent(UIUtils.getContext(), LoginActivity.class);
                    if(BaseActivity.runActivity instanceof MainActivity){
                        MainActivity activity= (MainActivity) BaseActivity.runActivity;
                        int tabIndex=activity.getCurrentTab();
                        //activity.switchCurrentTab(0);
                        activity.startActivityForResult(intent,tabIndex+10);
                    }else {
                        BaseActivity.runActivity.startActivity(intent);
                    }
                }
            }
    
    
            Log.i("YLLibrary","resultError");
            if(ex.getBaseInfo()!=null&&!TextUtils.isEmpty(ex.getBaseInfo().getHeader().getMessage()))
                UIUtils.showToast(ex.getBaseInfo().getHeader().getMessage());
    
            ApiException apiException = new ApiException(ex, ex.getErrCode());
            onApiError(apiException);
        }
    
        @Override
        public void onCompleted() {
            Log.i("YLLibrary","onCompleted");
            if(progressBar!=null)
                UIUtils.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        progressBar.setVisibility(View.GONE);
                        progressBar=null;
                    }
                });
        }
    
    }
    

    实际接口请求的代码,使用自定义异常回调的类——AbsAPICallback就可以统一处理异常:

            ApiRequestManager.createApi().problemDetail(dataBean.getId())
                    .compose(ApiRequestManager.<QuestionDetailInfo>applySchedulers())
                    .subscribe(new AbsAPICallback<QuestionDetailInfo>() {
                        @Override
                        public void onNext(QuestionDetailInfo baseInfo) {
                            fillData(baseInfo);
                        }
                    });
    

    更多精彩内容,关注微信公众账号「老于的笔记」,如果作品对您有所帮助,随意打赏



    作者:于连林520wcf
    链接:https://www.jianshu.com/p/a7c2ef4e0fae
    来源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
  • 相关阅读:
    TcpClient连接帮助类
    winform中添加管理员权限控制
    textbox中输入email做格式控制
    绑定和非绑定方法
    类的封装、property、多态和多态性
    继承类之点点滴滴
    爬虫日记-正则表达式
    爬虫日记-单元总结
    爬虫日记-最好大学排名实例
    爬虫日记-第一单元总结
  • 原文地址:https://www.cnblogs.com/Jeely/p/11314935.html
Copyright © 2020-2023  润新知