最近事比较多,距离上次写文章已经过去了一个月了。上一篇文章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();//构建RetrofitmRetrofit = 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 {@Overridepublic 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();//构建RetrofitmRetrofit = 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;}@Overridepublic Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,Retrofit retrofit) {//  TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));return new GsonResponseBodyConverter<>(gson, type);}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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() {@Overridepublic 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);}@Overridepublic void onCompleted() {Log.i("YLLibrary","onCompleted");if(progressBar!=null)UIUtils.runOnUiThread(new Runnable() {@Overridepublic void run() {progressBar.setVisibility(View.GONE);progressBar=null;}});}}
复制代码

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

        ApiRequestManager.createApi().problemDetail(dataBean.getId()).compose(ApiRequestManager.<QuestionDetailInfo>applySchedulers()).subscribe(new AbsAPICallback<QuestionDetailInfo>() {@Overridepublic void onNext(QuestionDetailInfo baseInfo) {fillData(baseInfo);}});
复制代码

Retrofit全攻略——进阶篇相关推荐

  1. ML与math:机器学习与高等数学基础概念、代码实现、案例应用之详细攻略——进阶篇

    ML与math:机器学习与高等数学基础概念.代码实现.案例应用之详细攻略--进阶篇 目录 人工智能数学基础综合 人工智能数学基础之高等数学 1.哈夫曼编码Huffman Coding简介及代码实现 人 ...

  2. 国企央企OFFER收割全攻略 | 银行篇之差额体检答疑

    文章目录 国企央企OFFER收割全攻略 | 银行篇之差额体检&答疑 6 体检 6.1 体检是差额体检,是招聘的一个环节 6.2 体检的流程 6.3 体检的注意事项 7 答疑 8 结束语 题外话 ...

  3. 国企央企OFFER收割全攻略 | 银行篇之面试

    文章目录 国企央企OFFER收割全攻略 | 银行篇之面试 5 面试 5.1 银行面试的考察内容与考察形式 5.2 不同银行面试的面试内容 5.3 银行面试流程与常考问题 5.4 如何准备银行面试 5. ...

  4. 国企央企OFFER收割全攻略 | 银行篇之笔试

    文章目录 国企央企OFFER收割全攻略 | 银行篇之笔试 4 笔试 4.1 银行笔试的考察内容与题型 4.2 不同银行笔试的侧重点 4.3 如何准备银行笔试 4.4 笔试的注意事项 小结 国企央企OF ...

  5. asp功放怎么装_汽车音响安装全攻略--功放篇

    汽车音响安装全攻略--功放篇 安装功放比安装接收机或扬声器更需要一些技巧,但并不意味着不可操作.事先了解注意事项,可以减少发生故障的机率. 安 装 第一步:作好准备工作 功放与接收机间必须保持至少90 ...

  6. 国企央企OFFER收割全攻略 | 银行篇之性格测评

    文章目录 国企央企OFFER收割全攻略 | 银行篇之性格测评 3 性格测评 3.1 考察内容 3.2 题型 3.3 注意事项 小结 国企央企OFFER收割全攻略 | 银行篇之性格测评 大家好,这里是小 ...

  7. 国企央企OFFER收割全攻略 | 银行篇之银行投递

    国企央企OFFER收割全攻略 | 银行篇之银行投递 大家好,这里是小黛.最近完成了一些工作上的事情,因此更新推迟了一些. 这篇银行求职全攻略,千呼万唤始出来,耗费了小黛非常非常非常多的心血,反复加工反 ...

  8. 国企央企OFFER收割全攻略 | 银行篇之行业整体介绍

    国企央企OFFER收割全攻略 | 银行篇之行业整体介绍 大家好,这里是小黛.最近完成了一些工作上的事情,因此更新推迟了一些. 这篇银行求职全攻略,千呼万唤始出来,耗费了小黛非常非常非常多的心血,反复加 ...

  9. FPGA开发全攻略——概念篇

    原文链接: FPGA开发全攻略连载之一:FPGA为什么这么热? FPGA开发全攻略连载之二:为什么工程师要掌握FPGA开发知识? FPGA开发全攻略连载之三:FPGA基本知识与发展趋势(part1) ...

最新文章

  1. 写个Vue小组件,图片滚动
  2. crysis3 android,Crytek谈安卓版《Crysis 3》:Tegra X1图形性能OK,瓶颈是CPU
  3. C语言关于static的解析
  4. HTML元素水平居中和垂直居中
  5. java中输入char类型_java中如何输入char类型
  6. python发邮件11002_Python学习笔记(二)——数字类型的运算及其输入与格式化输出...
  7. 有关web接受管理邮件
  8. 2018 年度总结 —— 缘见
  9. mysql8 2058_SQLyog连接MySQL8.0及以上版本出现2058错误解决方案
  10. linux内核分析作业3:跟踪分析Linux内核的启动过程
  11. 3c vrrp的接口监视_主备冗余协议,VRRP基础,状态机选举及VRRP配置,理论+实战...
  12. 【bzoj4007】[JLOI2015]战争调度 暴力+树形背包dp
  13. python企业级框架_Python六大开源框架对比:Web2py略胜一筹(转)
  14. 一组优秀的 for .NET Winform UI控件——DotNetMagic最新版本2.3,推荐使用
  15. 利用矩阵的逆(伪逆)与除法求解
  16. Mac 编译安装zlib
  17. sdlc esd oracle,SDLC-PCIE高速同步串口卡
  18. ciscn_2019_n_7(exit_hook)、wdb_2018_1st_babyheap(fsop的例子)
  19. 【总目录3】Python、神经网络与深度学习、毕业设计总结大全
  20. 重构 - 提炼函数,消除重复代码

热门文章

  1. 梦境交互:做个现代灵媒,考虑一下?
  2. AI 3.0》王飞跃教授推荐序——未来智能:人有人用,机有机用
  3. 2021机器智能研究方向
  4. 求助马斯克实现载人飞行,NASA省了近300亿美元
  5. Nature重大突破!将皮肤细胞直接转化成感光细胞让小鼠重见光明!
  6. PANS:智力以外的特质,会影响长期成就
  7. 图灵奖公布:高性能计算先驱、为超算铺平道路的Jack Dongarra获奖
  8. 上市 10 天就遭破解!AirTag 还能买吗?
  9. 程序员,技术的“背锅侠”,盘点 2020 年面向监狱编程的那些事!
  10. vue-router 按需加载的 3 种方式