最近事比较多,距离上次写文章已经过去了一个月了。上一篇文章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
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

Retrofit全攻略——进阶篇的更多相关文章

  1. Retrofit全攻略——基础篇

    实际开发过程中一般都会选择一些网络框架提升开发效率.随着Google对HttpClient 摒弃和Volley框架的逐渐没落.OkHttp開始异军突起.而Retrofit则对OkHttp进行了强制依赖 ...

  2. vscode 开发项目, Prettier ESLint的配置全攻略(基础篇)

    我们在做项目尤其是多人合作开发的时候经常会因为不同的开发规范和代码风格导致出现冲突, 为了能统一代码风格和规范我们需要使用到prettier和eslint,接下来就一vscode编辑器为例详细讲解下: ...

  3. 打造一个高逼格的android开源项目——小白全攻略 (转)

    转自:打造一个高逼格的android开源项目 小引子 在平时的开发过程中,我们经常会查阅很多的资料,最常参考的是 github 的开源项目.通常在项目的主页面能看到项目的简介和基本使用,并且时不时能看 ...

  4. Android-x86虚拟机安装配置全攻略

    转自Android-x86虚拟机安装配置全攻略 注:这里安装从简,具体请参考虚拟机Vmware安装运行安卓4.0详细教程 Android-x86虚拟机安装配置网上有很多,但是全部说明白的确不多,希望这 ...

  5. [置顶] 创建GitHub技术博客全攻略

    [置顶] 创建GitHub技术博客全攻略 分类: GitHub2014-07-12 13:10 19710人阅读 评论(21) 收藏 举报 githubio技术博客网站生成 说明: 首先,你需要注册一 ...

  6. QTP DataTable全攻略(1)

    上一篇 / 下一篇  2009-07-27 00:14:16 / 个人分类:qtp 查看( 575 ) / 评论( 0 ) / 评分( 0 / 0 ) 下面的代码可能有点乱,基本涉及到常用的datat ...

  7. VS2013全攻略(安装,技巧,快捷键,插件)!

    工欲善其事,必先利其器.VS2013全攻略(安装,技巧,快捷键,插件)! 之前一篇<c++的性能, c#的产能?!鱼和熊掌可以兼得,.NET NATIVE初窥>承蒙大家喜爱和编辑推荐,在此 ...

  8. 网络唤醒全攻略(Wake On Lan)

    家里组了台服务器存放资料,或者作为开发服务器,远程登陆成为刚性需求,由于机器需要的时候才用到,所以如果经常开机的话很费电,按需开机是最佳办法:网上教程很多,但是比较杂乱,表达累赘:还是自己总结一篇简单 ...

  9. VSCode插件开发全攻略(一)概览

    文章索引 VSCode插件开发全攻略(一)概览 VSCode插件开发全攻略(二)HelloWord VSCode插件开发全攻略(三)package.json详解 VSCode插件开发全攻略(四)命令. ...

随机推荐

  1. 如何编写高性能的 javascript

    一.Javascript代码执行效率1. DOM1.1 使用 DocumentFragment 优化多次 append说明:添加多个 dom 元素时,先将元素 append 到 DocumentFra ...

  2. C++ Compiling… Error spawning cl.exe

    转自VC错误:http://www.vcerror.com/?p=500 解决方法: 方法(一): 启动VC时不要用图形界面,通过在命令提示符下输入:Msdev /useenv运行(注意啦/前面有个空 ...

  3. 什么是AngularJs?特点是什么?和JQuery什么区别和联系

    什么是AngularJs? AngularJs是js框架,集中操作数据,不关注Dom操作,适用于以数据操作为主的的SPA(单页应用). 它的特点 采用MVC模型 双向数据绑定 依赖注入 模块化 与jQ ...

  4. upc组队赛5 Ground Defense【枚举】

    Ground Defense 题目描述 You are a denizen of Linetopia, whose n major cities happen to be equally spaced ...

  5. 深入理解javascript原型和闭包(1)——一切都是对象 (转载)

    深入理解javascript原型和闭包(1)——一切都是对象  http://www.cnblogs.com/wangfupeng1988/p/3977987.html “一切都是对象”这句话的重点在 ...

  6. c++计算1到100以内的质数

    自考c++实践的时候,有个求计算1-100的质数的问题,没搞出来 由于考试使用的是Dev-C++开发工具,为了下次考试做准备,改用该工具,直接下载安装即可,不会涉及到什么破解等 下载地址:https: ...

  7. [已解决]报错: Version in docker-compose is unsupported

    docker compose将解析版本为"2",而不是"3.3".应该改为: version: "2"

  8. javascript中var同时声明多个变量时的原理是什么?

    <script> function show(){ var a=b=c=d=5; } show(); alert(a);//弹a时报错(not defined),而b.c.d都能弹出5 & ...

  9. vue 中引入cryptoJS

    在搞前端开发的时候,页面上有很多的地方是需要用户输入信息的,但是有些信息又很敏感,比如客户的姓名.电话号码.身份证号码.银行卡号及密码等等这些,如果没有进行加密处理,很容易被别人截取到,项目中应用到c ...

  10. JS浏览器事件循环机制

    文章来自我的 github 博客,包括技术输出和学习笔记,欢迎star. 先来明白些概念性内容. 进程.线程 进程是系统分配的独立资源,是 CPU 资源分配的基本单位,进程是由一个或者多个线程组成的. ...