本文出自 “阿敏其人” 简书博客,转载或引用请注明出处。
一、为什么用Retrofit
1、因为Okhttp很牛逼
2、因为RxJava很热
因为Retrofit封装了okhttp,又因为RxJava和Retrofit的关系就像下雨天和巧克力。所以,折腾Retrofit。
RxJava参考链接
二、Retrofit初步应用
build.gradle引入相关库
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'com.google.code.gson:gson:2.7'
开始最简单的demo
我们要请求网络数据了,那么来一个接口,get请求天气数据。
http://apistore.baidu.com/microservice/weather?citypinyin=beijing
典型的的Json返回类型:
{
code:0,
msg:"success"
data:{...}
}
实际返回:
{
"errNum": 0,
"errMsg": "success",
"retData": {
"city": "接口已经停用",
"pinyin": "apistore.baidu.com",
"citycode": "000000000",
"date": "201616-05-12",
"time": "10:00",
"postCode": "0000000",
"longitude": 0,
"latitude": 0,
"altitude": "0",
"weather": "多云",
"temp": "0",
"l_tmp": "0",
"h_tmp": "0",
"WD": "北风",
"WS": "接口已经停用,请访问APIStore.baidu.com查找对应接口",
"sunrise": "00:00",
"sunset": "00:00"
}
}
(虽显停用,无碍使用)
弄一个实体Bean吧,为了方便起见,public修饰
public class WeatherBean {
public int errNum;
public String errMsg;
public RetDataBean retData;
public static class RetDataBean {
public String city;
public String pinyin;
public String citycode;
public String date;
public String time;
public String postCode;
public int longitude;
public int latitude;
public String altitude;
public String weather;
public String temp;
public String l_tmp;
public String h_tmp;
public String WD;
public String WS;
public String sunrise;
public String sunset;
}
}
权限勿忘
<uses-permission android:name="android.permission.INTERNET"/>
准备工作完毕
来来来,解析解析:
准备一个WeatherApi 接口
public interface WeatherApi {
// 发送get请求,光是这段地址肯定不完整,待会我们还在在别的地方补上 baseurl 。 这里直接把后面的请求给写死了(这样肯定不好)
@GET("/microservice/weather?citypinyin=beijing")
Call<WeatherBean> getWeather();
}
MainActivity代码
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
String baseUrl = "http://apistore.baidu.com/";
// 创建一个 retrofit ,baseUrl必须有
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create()) // 用到了 com.squareup.retrofit2:adapter-rxjava:2.1.0'
.build();
// 利用 retrofit 创建一个接口
WeatherApi apiService = retrofit.create(WeatherApi.class);
// 利用接口创建一个Call对象
Call<WeatherBean> weatherBeanCall = apiService.getWeather();
// 请求入队,回调请求成功或者失败
weatherBeanCall.enqueue(new Callback<WeatherBean>() {
@Override
public void onResponse(Call<WeatherBean> call, Response<WeatherBean> response) {
System.out.println("====请求成功:"+response.body().retData.weather);
}
@Override
public void onFailure(Call<WeatherBean> call, Throwable t) {
System.out.println("====请求失败");
}
});
}
}
.
.
运行打印结果:
====请求成功:多云
第一次就这么跑下来了。
代码分析
(数据和bean的就不说了,只从Retroift说起)
- 第一步、准备一个接口,比如名为WeatherApi,接口里面通过注解的方式说明请求方式,比如这里我们指定为GET。
public interface WeatherApi {
// 发送get请求,光是这段地址肯定不完整,待会我们还在在别的地方补上 baseurl 。 这里直接把后面的请求给写死了(这样肯定不好)
@GET("/microservice/weather?citypinyin=beijing")
Call<WeatherBean> getWeather();
}
(GET当然可以传参,我们这里先通过一个不规范的行为写死了,后面会补充会有更好的方式)
第二步,创建一个retrofit 对象,传入对象baseUrl,指定数据将解析为何种对象。
.baseUrl(baseUrl),好理解
.addConverterFactory(GsonConverterFactory.create()) 将数据解析成Gson对象
.build就成功创建 retrofit 对象了第三步,利用retrofit对象得到一个接口实例。
第四步,利用接口实例得到Call对象,让Call对象异步入队。入队支队就会有两个回调方法 ,成功还是失败。
大概流程就是这么走的。
get传参
我们改一下代码
在WeatherApi里面,利用@Query可以传参
public interface APIService {
// 发送get请求,光是这段地址肯定不完整,待会我们还在在别的地方补上 baseurl 。 这里直接把后面的请求给写死了(这样肯定不好)
// 请求还是Get
@GET("/microservice/weather")
Call<WeatherBean> getWeather(@Query("citypinyin") String city); // 使用了@Query,后面的 citypinyin 是参数名
}
get方式的url里面,问号不用写,@Query("citypinyin")是key,String city是值类型和参数名。
MainActivity也需要修改下
private void getData() {
String baseUrl = "http://apistore.baidu.com/";
// 创建一个 retrofit ,baseUrl必须有
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create()) // 用到了 com.squareup.retrofit2:adapter-rxjava:2.1.0'
.build();
// 利用 retrofit 创建一个接口
WeatherApi apiService = retrofit.create(WeatherApi.class);
// 利用接口创建一个Call对象
Call<WeatherBean> weatherBeanCall = apiService.getWeather("beijing");
// 请求入队,回调请求成功或者失败
weatherBeanCall.enqueue(new Callback<WeatherBean>() {
@Override
public void onResponse(Call<WeatherBean> call, Response<WeatherBean> response) {
System.out.println("====请求成功:"+response.body().retData.weather);
}
@Override
public void onFailure(Call<WeatherBean> call, Throwable t) {
System.out.println("====请求失败");
}
});
}
改动的只有一行代码
Call<WeatherBean> weatherBeanCall = apiService.getWeather("beijing");
打印结果一致。
除了@Query传参,还由@Path等,请求方式里面有Get,Post等也是支持的,这里就不演示了。
二、Retrofit + RxJava
引入多两个库
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'com.google.code.gson:gson:2.7'
compile 'io.reactivex:rxjava:1.1.6'
compile 'io.reactivex:rxandroid:1.2.1'
简单结合
既然引入RxJava,那么就应该是 观察者 和 被观察者。
我们先看看在原有的代码里面观察者和被观察者的角色分别是谁在扮演
被观察者
在上面的代码中,我们的 apiService 扮演者 被观察者 的角色,所以在接口里面的返回值我们就不可以用Call,而应该用 Observable
public interface WeatherApi {
@GET("/microservice/weather")
Observable<WeatherBean> getWeather(@Query("citypinyin") String city);
}
观察者
在上面的分析图里面,new Callback<WeatherBean> 扮演的是 观察者 的角色,所以要做一个调整
如下
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
String baseUrl = "http://apistore.baidu.com/";
// 创建一个 retrofit ,baseUrl必须有
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // //添加 RxJava 适配器
.build();
// 利用 retrofit 创建一个接口
WeatherApi apiService = retrofit.create(WeatherApi.class);
// 被观察者
Observable observable = apiService.getWeather("beijing")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
// 观察者
Subscriber<WeatherBean> subscriber = new Subscriber<WeatherBean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
System.out.println("====请求失败");
}
@Override
public void onNext(WeatherBean weatherBean) {
System.out.println("====请求成功:" + weatherBean.retData.weather);
}
};
// 产生订阅关系
observable.subscribe(subscriber);
}
}
在上面 观察者 和 被观察者 只要熟悉RxJava的可以理解。
** RxJavaCallAdapterFactory **
但是需要注意的是,retrofit 的配置里面要加多一句 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // //添加 RxJava 适配器
这个很重要
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // //添加 RxJava 适配器
.build();
CallAdapter.Factory 是 Retrofit 这个库中的接口,用来给我们自定义去解析我们自己想要的类型用的。
举个栗子:
@GET("/aaa")Observable<QuestionListData> getQuestionNewestList();
比如这么一个接口, retrofit本身是无法识别 Observable<QuestionListData>然后去工作的,如果没有这个适配器就根本无法工作,因此我们的适配器的作用,就是生成我们想要的 Observable 。
三、封装请求
在上面的代码中,每次请求都需要new 一个Retrofit实例,这样肯定是不好的。
所以我们弄一个单例,把请求封装起来。
封装代码:
public class HttpMethods {
public static final String BASE_URL = "http://apistore.baidu.com/";
private static final int DEFAULT_TIMEOUT = 5;
private Retrofit retrofit;
private WeatherApi weatherApi;
//构造方法私有
private HttpMethods() {
//手动创建一个OkHttpClient并设置超时时间
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.client(httpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
weatherApi = retrofit.create(WeatherApi.class);
}
//在访问HttpMethods时创建单例
private static class SingletonHolder{
private static final HttpMethods INSTANCE = new HttpMethods();
}
//获取单例
public static HttpMethods getInstance(){
return SingletonHolder.INSTANCE;
}
public void getWeather(Subscriber<WeatherBean> subscriber, String city){
weatherApi.getWeather(city)
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
}
接下来ManActivity自己要这么做就好
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
// 观察者
Subscriber<WeatherBean> subscriber = new Subscriber<WeatherBean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
System.out.println("====请求失败");
}
@Override
public void onNext(WeatherBean weatherBean) {
System.out.println("====请求成功:" + weatherBean.retData.weather);
}
};
// 产生订阅关系
HttpMethods.getInstance().getWeather(subscriber,"beijing");
}
}
几乎一行代码完成。
四、服务器返回数据统一处理
比如服务器返回的数据是统一是如下格式:
{
code:0,
msg:"success"
data:{...}
}
code和msg是固定格式int和String,data不固定,可能是数组,可能是一个对象。
那么这个data就是泛型T
然后在Activity和Fragment里面我不关心code和msg,我们只关心data。
对于code我们可以做一个预处理,如果预处理code不等于0,那么就直接执行onError了。
我们把WeatherBean删掉,实体Bean我们可以拆分成这样
这么做之后,自然很多报错,那么来解决。
第一处修改
Observable参数类型换成 GeneralResults<RetDataBean>
public interface WeatherApi {
@GET("/microservice/weather")
Observable<GeneralResults<RetDataBean>> getWeather(@Query("citypinyin") String city);
}
第二处
public class HttpMethods {
public static final String BASE_URL = "http://apistore.baidu.com/";
private static final int DEFAULT_TIMEOUT = 5;
private Retrofit retrofit;
private WeatherApi weatherApi;
//构造方法私有
private HttpMethods() {
//手动创建一个OkHttpClient并设置超时时间
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.client(httpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
weatherApi = retrofit.create(WeatherApi.class);
}
//在访问HttpMethods时创建单例
private static class SingletonHolder{
private static final HttpMethods INSTANCE = new HttpMethods();
}
//获取单例
public static HttpMethods getInstance(){
return SingletonHolder.INSTANCE;
}
public void getWeather(Subscriber<GeneralResults<RetDataBean>> subscriber, String city){
weatherApi.getWeather(city)
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
}
第三处:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
// 观察者
Subscriber<GeneralResults<RetDataBean>> subscriber = new Subscriber<GeneralResults<RetDataBean>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
System.out.println("====请求失败");
}
@Override
public void onNext(GeneralResults<RetDataBean> weatherBean) {
System.out.println("====请求成功:" + weatherBean.retData.weather);
}
};
// 产生订阅关系
HttpMethods.getInstance().getWeather(subscriber,"beijing");
}
}
依然结果不变,你可以说,这不就是把相关的5个原先的WeatherBean换成GeneralResults<RetDataBean>吗,是的没错,有这个认识挺好的。
我们先来想一下,这5个地方的类型需要完全一致吗?
对于 被观察者 来说,也就是WeatherApi里面的返回值Observable,这里的参数类型需要跟返回的Json数据对应,这样才靠谱
对于 观察者 Subscriber来说,他的类型是不需要跟着Observable一样的,但是直接写不一样的类型肯定不可以,但是我们可以通过map变换等把 被观察者 传入的数据类型 转换成 观察者 想要的其他类型。
我们知道,被观察者需要的是 GeneralResults<RetDataBean> ,但是观察者需要的是 RetDataBean 而已。
也就是说,我们可以把 被观察者 传入的GeneralResults<RetDataBean> 变换成 观察者 想要的 RetDataBean。
根据上面的代码,我们接着来。
利用变换统一处理返回的数据类型。
被观察者需要的是 GeneralResults<RetDataBean> ,但是观察者需要的是 RetDataBean 而已。
这里我们用map进行变换
GeneralResults<RetDataBean>变RetDataBean 是一个一对一的操作。
如果data是一个数组或者集合,那么我们可以用flatMap,平铺变换,这个支持一对多。
上代码:
WeatherApi
public interface WeatherApi {
@GET("/microservice/weather")
Observable<GeneralResults<RetDataBean>> getWeather(@Query("citypinyin") String city);
}
上图中,HttpMethods 采用的是map变换,下面的实际代码中我们为了演示多一种用法,我们采用flatMap
(虽然在当前情况下不需要flatMap,但是flatMap依然可以良好地完成工作)
HttpMethods
public class HttpMethods {
public static final String BASE_URL = "http://apistore.baidu.com/";
private static final int DEFAULT_TIMEOUT = 5;
private Retrofit retrofit;
private WeatherApi weatherApi;
//构造方法私有
private HttpMethods() {
//手动创建一个OkHttpClient并设置超时时间
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.client(httpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
weatherApi = retrofit.create(WeatherApi.class);
}
//在访问HttpMethods时创建单例
private static class SingletonHolder{
private static final HttpMethods INSTANCE = new HttpMethods();
}
//获取单例
public static HttpMethods getInstance(){
return SingletonHolder.INSTANCE;
}
public void getWeather(Subscriber<RetDataBean> subscriber, String city){
weatherApi.getWeather(city)
.flatMap(new Func1<GeneralResults<RetDataBean>, Observable<RetDataBean>>() {
@Override
public Observable<RetDataBean> call(GeneralResults<RetDataBean> retDataBeanGeneralResults) {
return flatResult(retDataBeanGeneralResults);
}
})
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
static <T> Observable<T> flatResult(final GeneralResults<T> result) {
return Observable.create(new Observable.OnSubscribe<T>() {
@Override
public void call(Subscriber<? super T> subscriber) {
if (result.errNum!= 0) {
subscriber.onError(new ApiException(ApiException.USER_NOT_EXIST));
} else{
subscriber.onNext(result.retData);
}
subscriber.onCompleted();
}
});
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getData();
}
private void getData() {
// 观察者
Subscriber<RetDataBean> subscriber = new Subscriber<RetDataBean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
System.out.println("====请求失败");
}
@Override
public void onNext(RetDataBean weatherBean) {
System.out.println("====请求成功:" + weatherBean.weather);
}
};
// 产生订阅关系
HttpMethods.getInstance().getWeather(subscriber,"beijing");
}
}
WeatherApi
public interface WeatherApi {
@GET("/microservice/weather")
Observable<GeneralResults<RetDataBean>> getWeather(@Query("citypinyin") String city);
}
ApiException
public class ApiException extends RuntimeException {
public static final int USER_NOT_EXIST = 100;
public static final int WRONG_PASSWORD = 101;
public ApiException(int resultCode) {
this(getApiExceptionMessage(resultCode));
}
public ApiException(String detailMessage) {
super(detailMessage);
}
/**
* 由于服务器传递过来的错误信息直接给用户看的话,用户未必能够理解
* 需要根据错误码对错误信息进行一个转换,在显示给用户
* @param code
* @return
*/
private static String getApiExceptionMessage(int code){
String message = "";
switch (code) {
case USER_NOT_EXIST:
message = "该用户不存在";
break;
case WRONG_PASSWORD:
message = "密码错误";
break;
default:
message = "未知错误";
}
return message;
}
}
运行效果一致。
五、如何去掉调用
如果没有使用Rxjava,那么Service返回的是一个Call,而这个Call对象有一个cancel方法可以用来取消Http请求。那么用了Rxjava之后,如何来取消一个请求呢
我们在Activity或者Fragment中创建subscriber对象,想要取消请求的时候调用subscriber的unsubscribe方法就可以了。
六、加载时的Dialog
加载时,dilalog弹出
完成、错误时dialog消失
dialog跟订阅关系同步
ProgressCancelListener
public interface ProgressCancelListener {
void onCancelProgress();
}
.
.
ProgressDialogHandler
public class ProgressDialogHandler extends Handler {
public static final int SHOW_PROGRESS_DIALOG = 1;
public static final int DISMISS_PROGRESS_DIALOG = 2;
private ProgressDialog pd;
private Context context;
private boolean cancelable;
private ProgressCancelListener mProgressCancelListener;
public ProgressDialogHandler(Context context, ProgressCancelListener mProgressCancelListener,
boolean cancelable) {
super();
this.context = context;
this.mProgressCancelListener = mProgressCancelListener;
this.cancelable = cancelable;
}
private void initProgressDialog(){
if (pd == null) {
pd = new ProgressDialog(context);
pd.setCancelable(cancelable);
if (cancelable) {
pd.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
mProgressCancelListener.onCancelProgress();
}
});
}
if (!pd.isShowing()) {
pd.show();
}
}
}
private void dismissProgressDialog(){
if (pd != null) {
pd.dismiss();
pd = null;
}
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PROGRESS_DIALOG:
initProgressDialog();
break;
case DISMISS_PROGRESS_DIALOG:
dismissProgressDialog();
break;
}
}
}
.
.
SubscriberOnNextListener
public interface SubscriberOnNextListener<T> {
void onNext(T t);
}
.
.
ProgressSubscriber
public class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener {
private SubscriberOnNextListener mSubscriberOnNextListener;
private ProgressDialogHandler mProgressDialogHandler;
private Context context;
public ProgressSubscriber(SubscriberOnNextListener mSubscriberOnNextListener, Context context) {
this.mSubscriberOnNextListener = mSubscriberOnNextListener;
this.context = context;
mProgressDialogHandler = new ProgressDialogHandler(context, this, true);
}
private void showProgressDialog() {
if (mProgressDialogHandler != null) {
mProgressDialogHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget();
}
}
private void dismissProgressDialog() {
if (mProgressDialogHandler != null) {
mProgressDialogHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget();
mProgressDialogHandler = null;
}
}
@Override
public void onStart() {
showProgressDialog();
}
@Override
public void onCompleted() {
dismissProgressDialog();
Toast.makeText(context, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(Throwable e) {
dismissProgressDialog();
Toast.makeText(context, "error:" + e.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onNext(T t) {
mSubscriberOnNextListener.onNext(t);
}
@Override
public void onCancelProgress() {
if (!this.isUnsubscribed()) {
this.unsubscribe();
}
}
}
最后,MainActivity
public class MainActivity extends AppCompatActivity {
private TextView mTvGet;
private TextView mTvResult;
private SubscriberOnNextListener getWeatherNext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvGet = (TextView) findViewById(R.id.mTvGet);
mTvResult = (TextView) findViewById(R.id.mTvResult);
mTvGet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
HttpMethods.getInstance().getWeather(new ProgressSubscriber(getWeatherNext, MainActivity.this), "beijing");
}
});
getWeatherNext = new SubscriberOnNextListener<RetDataBean>() {
@Override
public void onNext(RetDataBean retDataBean) {
mTvResult.setText("==========:"+retDataBean.weather);
}
};
}
}
有点困,几个接口不分析了。
本篇完。
参考学习: