retrofit2是一款封装okhttp的优秀的网络请求框架,搭配rxjava时有出人意料的效果,每当出去面试的时候,被问及的频率也是极高的,于是新项目里也就顺理成章的接入了retrofit2+rxjava,并做了简易的封装,趁着今天周六,写一篇文章来记录一下。
github:https://github.com/square/retrofit
我们先来使用retrofit进行一个简单的网络请求,获取必应搜索的每日一图,这是一个简单的get请求,返回数据为一个json对象,每日一图的地址需要从对象里来获取。
api:https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN
1.1先定义一个接口:
public interface NetService {
@GET("HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN")
Call getImage();
}
baseUrl截取为 https://cn.bing.com/ 作为一个常量,放在常量类中。FirstImageBean为返回的json对应的实体类,可使用gsonformat直接生成,不做赘述。
1.2.在activity中进行请求
public void getImage() {
// 1.获取retrofit对象
Retrofit build = new Retrofit.Builder()
.baseUrl(UrlConstant.URL_BING1)
.addConverterFactory(GsonConverterFactory.create())//指定json解析
.build();
// 获取接口类对象
NetService netService = build.create(NetService.class);
netService.getImage().enqueue(new Callback() {
@Override
public void onResponse(Call call, retrofit2.Response response) {
String url = response.body().getImages().get(0).getUrl();
}
@Override
public void onFailure(Call call, Throwable t) {
LogUtil.i("请求失败:" + t.getMessage());
}
});
}
2.1重新定义接口,这次我们返回的是个Obervable对象
@GET("HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN")
Observable getBingDayImage();
2.2.在Activty中进行请求
/**
* 获取必应搜索每日图片
*/
private void getBingImage() {
OkHttpClient build = new OkHttpClient.Builder().build();
Retrofit retrofit = new Retrofit.Builder()
.client(build)
.baseUrl(UrlConstant.URL_BING1)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
NetService netService = retrofit.create(NetService.class);
//获取每日一图
netService.getBingDayImage()
.subscribeOn(Schedulers.io())//
.observeOn(AndroidSchedulers.mainThread())//指定观察者执行在主线程
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) { }
@Override
public void onNext(FirstImageBean firstImageBean) {
image_url = UrlConstant.URL_BING + firstImageBean.getImages().get(0).getUrl();
LogUtil.i("retrofit:" + image_url);
mHandler.sendEmptyMessage(0);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
可以看到,在构建retrofit对象的时候,我我们加了.addCallAdapterFactory(RxJava2CallAdapterFactory.create()),这句话为添加rxjava时所必须的。
经过前2步,我们可以看到使用retrofit有很多代码都是重复的,所以我们可以写个管理类出来,统一提供出一个retrofit对象,甚至可以统一提供出一个接口对象,这样就避免了很多重复的代码。
public class Retrofit2Util {
private static Retrofit2Util retrofit2Util;
private static Retrofit retrofitClient;
private static OkHttpClient client;
private Retrofit2Util() { }
public static Retrofit2Util getInstance() {
if (retrofit2Util == null) {
synchronized (Retrofit2Util.class) {
if (retrofit2Util == null) {
retrofit2Util = new Retrofit2Util();
client = getClient();
retrofitClient = getRetrofit();
}
}
}
return retrofit2Util;
}
public static Retrofit getRetrofit() {
if (retrofitClient == null) {
synchronized (Retrofit2Util.class) {
if (retrofitClient == null)
retrofitClient = new Retrofit.Builder()
.client(client)
.baseUrl(UrlConStant.URL_BASE)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
}
return retrofitClient;
}
public static OkHttpClient getClient() {
OkHttpClient build = new OkHttpClient.Builder()
.addInterceptor(getLoggerInterceptor())//打印日志拦截器
.addInterceptor(getInterceptor())//添加公共参数拦截器
.build();
return build;
}
public NetService getNetService(){
NetService netService = retrofitClient.create(NetService.class);
return netService;
}
/**
* 基础公共参数适配器
*
* @return
*/
private static CommonParamsInterceptor getInterceptor() {
return new CommonParamsInterceptor();
}
/**
* 拦截器 - 打印日志
* @return
*/
private static Interceptor getLoggerInterceptor() {
Interceptor interceptor = new okhttp3.logging.HttpLoggingInterceptor(new HttpLog()).setLevel(HttpLoggingInterceptor.Level.BODY);
return interceptor;
}
}
可以看到,我们在这个工具类里采用单例模式初始化了一个retrofit对象,并提供出一个静态方法来供外部调用。
在代码中进行网络请求时,我们可以这样来写:
Retrofit2Util.getInstance()
.getNetService()..
这么一看,是不是节省了很多的重复代码?
同时在实际开发中,我们的返回实体类一般包含一个状态码,一个描述信息以及一个实体类,这样我们可以做个简单的基类:
public class BaseResponse {
private int status;
private String msg;
private T data;
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
现在再来看一下我们对返回结果的处理上,但用retrofit时,回调方法只有2个,即一个成功,一个失败,清楚明了,但是我们加上rxjava之后,需要重写四个回调方法,极度影响代码阅读,毕竟我们只希望看到2个结果。
这就需要我们对Observable再做一个简单的封装了。即自定义一个BaseObservable,重写它的四个方法,并提供两个抽象方法:成功和失败,在相应的方法中执行,这样在我们使用的时候,就可以只重写2个方法就OK了。具体操作如下:
public abstract class BaseObserver implements Observer {
public BaseObserver() { }
@Override
public void onSubscribe(Disposable d) { }
@Override
public void onComplete() {
onFinish();
}
@Override
public void onError(Throwable e) {
onFailure(-1,e.getMessage());
onFinish();
}
protected void onFinish(){}
@Override
public void onNext(T baseResponse) {
if (baseResponse.getStatus()==0){
onSuccess(baseResponse);
}else {
onFailure(baseResponse.getStatus(),baseResponse.getMsg());
}
}
public abstract void onSuccess(T baseResponse);
public abstract void onFailure(int errcode, String errMsg);
}
这样,在实际开发中,我们的返回结果处理可以这样写:
..subscribe(new BaseObserver>>() {
@Override
public void onSuccess(BaseResponse> baseResponse{
}
@Override
public void onFailure(int errcode, String errMsg) {
}
});
从代码量上来说,我们的代码进一步减少了。
现在再来看一下我们在activity中调用网络请求:
Retrofit2Util.getInstance()
.getNetService()
.getEarHome()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new BaseObserver>() {
@Override
public void onSuccess(BaseResponse baseResponse{
}
}
@Override
public void onFailure(int errcode, String errMsg) {
}
});
是不是简单了很多呢?
现实开发中我们常常会携带几个公共参数去后台进行请求,比如当前的手机平台,是ios还是Android,app的版本号,以及用户userID,token等等,在每个网络接口里都加上四个参数的方法不是不能用,单就是显得逼格不高,那有没有什么方法可以一劳永逸呢?当然是有的,可以通过okhttp的拦截器来做。
回头看看我们的RetrofitUtils类,我有添加了两个拦截器:
public static OkHttpClient getClient() {
OkHttpClient build = new OkHttpClient.Builder()
.addInterceptor(getLoggerInterceptor())//打印日志拦截器
.addInterceptor(getInterceptor())//添加公共参数拦截器
.build();
return build;
}
/**
* 基础公共参数拦截器
*
* @return
*/
private static CommonParamsInterceptor getInterceptor() {
return new CommonParamsInterceptor();
}
/**
* 拦截器 - 打印日志
* @return
*/
private static Interceptor getLoggerInterceptor() {
Interceptor interceptor = new okhttp3.logging.HttpLoggingInterceptor(new HttpLog()).setLevel(HttpLoggingInterceptor.Level.BODY);
return interceptor;
}
拦截器是okhttp一个很优秀的机制,retrofit是基于okhtpp的,所以这一点也被继承了过来,那先来看一看公共参数到底是怎么添加的吧:
/**
* 网络 拦截器 添加公共参数
* Created by zhangyanpeng on 2019/10/9
*/
public class CommonParamsInterceptor implements Interceptor {
private static final String TYPE_GET = "GET";
private static final String TYPE_POST = "POST";
private static final String DEVICE_TYPE = "android";
private static final String TOKEN = LocalDataSourceImpl.getInstance().getToken();
private static final String DEVICE_ID = Build.SERIAL;
private static final String APP_VERSION = AppUtils.getAppVersionName();
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (request.method().equals(TYPE_GET)) {
request = addParamsGet(request);
} else if (request.method().equals(TYPE_POST)) {
request = addParamsPost(request);
}
return chain.proceed(request);
}
/**
* POST请求添加公共参数
*/
private Request addParamsPost(Request request) {
if (request.body() instanceof FormBody) {
FormBody body = (FormBody) request.body();
FormBody.Builder builder = new FormBody.Builder();
// 复制原来的参数
for (int i = 0; i < body.size(); i++) {
builder.addEncoded(body.encodedName(i), body.encodedValue(i));
}
// 添加公共参数
builder.add("token", TOKEN);
builder.add("deviceID", DEVICE_ID);
builder.add("deviceType", DEVICE_TYPE);
builder.add("appVersion", APP_VERSION);
Request build = request.newBuilder().post(builder.build()).build();//构建新的请求
return build;
} else {
// 无formbady 时 调用此方法,添加公共参数
FormBody build = new FormBody.Builder()
.add("token", TOKEN)
.add("deviceID", DEVICE_ID)
.add("deviceType", DEVICE_TYPE)
.add("appVersion", APP_VERSION)
.build();
Request reque = request.newBuilder().post(build).build();
return reque;
}
}
/**
* get请求 添加公共参数
*/
private Request addParamsGet(Request request) {
HttpUrl url = request.url().newBuilder()
.addQueryParameter("token", TOKEN)
.addQueryParameter("deviceID", DEVICE_ID)
.addQueryParameter("deviceType", DEVICE_TYPE)
.addQueryParameter("appVersion", APP_VERSION)
.build();
Request build = request.newBuilder().url(url).build();
return build;
}
}
可以看到原理很简单,重写拦截器,可以获取到网络请求的request对象,其中包含有我们的请求体,我们拿到请求体之后重新添加公共参数即可。
日志打印拦截器也是类似的原理:
public class HttpLog implements HttpLoggingInterceptor.Logger {
@Override
public void log(String message) {
LogUtil.i(message);
}
}
好了,到这一步,我们的网络请求封装的基础版已经封装完毕,可以总结一下我们都做了那些工作:
1.自定义retrofit工具类,提供retrofit单例对象以及网络请求的接口对象。
2.自定义BaseObservable和基础的返回对象,方便对返回结果做处理。
3.自定义拦截器,方便添加公共参数,以及打印出网络请求和返回的参数。
经过这三步之后,我们在Activity中就可以采用链式代码进行请求。
当然这都是最基础的一些封装,以后有空可以做进一步的封装。