前言
本文参考:
RxJava 与 Retrofit 结合的最佳实践http://gank.io/post/56e80c2c677659311bed9841
与上文不同的是:
- 本文采用最新的RxJava2.0与Retrifit2.0来实现,并针对于1.x不同的地方进行处理。
- 针对请求过程进行的封装,额外增加缓存策略和请求头部处理。
- RxJava2.0使用笔记:http://blog.csdn.net/demonliuhui/article/details/77848691
- Retrifit2.0使用笔记:http://blog.csdn.net/demonliuhui/article/details/77854530
使用配置
添加依赖
dependencies {
...
compile 'io.reactivex.rxjava2:rxjava:2.1.3'
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.squareup.okhttp3:okhttp:3.9.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.9.0'
...
}
注意:
- 添加RxAndroid是为了解决线程调度问题。
okhttp3:logging-interceptor
是为了配置缓存策略,必须跟okhttp同一版本号,否则会报错:Failed resolution of: Lokhttp3/internal/Platform
添加权限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
测试Api
豆瓣电影的Top250做测试连接,目标地址为:
返回的数据格式,大家自己访问下链接就看到了。
创建数据实体类
知道数据格式后我们创建对应数据实体类,方便处理获取后的数据。
这里只是简单获取几个数据,大家可以根据自己需求去编写自己想要的数据。
public class Movie {
private String title;
private List<Subjects> subjects;
public String getTitle() {
return title;
}
public List<Subjects> getSubjects() {
return subjects;
}
}
public class Subjects {
private String title, year, id;
public Subjects(String title, String year, String id) {
this.title = title;
this.year = year;
this.id = id;
}
public String getTitle() {
return title;
}
public String getYear() {
return year;
}
public String getId() {
return id;
}
}
最基本的RxJava+Retrofit
1.创建Service类
因为要结合使用RxJava,所以返回值就不在是一个Call了,而是一个Observable。
public interface ApiService {
@GET("top250")
Observable<Movie> getTopMovie(@Query("start") int start, @Query("count") int count);
}
2.创建Retrofit请求过程
String baseUrl = "https://api.douban.com/v2/movie/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
//请求的结果转为实体类
.addConverterFactory(GsonConverterFactory.create())
//适配RxJava2.0,RxJava1.x则为RxJavaCallAdapterFactory.create()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
3.Activity发出请求(RxJava订阅)及处理数据
注意:RxJava2.0开始subscribe的对象不能是Subscriber,只能是Observer和Consumer,为了处理完整的过程我们这里选择Observer。 onSubscribe等同于onStart。
apiService.getTopMovie(0, 10)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Movie>() {
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe: ");
}
@Override
public void onNext(Movie movie) {
Log.d(TAG, "onNext: " + movie.getTitle());
List<Subjects> list = movie.getSubjects();
for (Subjects sub : list) {
Log.d(TAG, "onNext: " + sub.getId() + "," + sub.getYear() + "," + sub.getTitle());
}
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete: Over!");
}
});
4.运行结果
09-06 14:25:09.151 30008-30008/eee.rxjavaretrofit D/MainActivity: onSubscribe:
09-06 14:25:11.308 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 豆瓣电影Top250
09-06 14:25:11.308 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1292052,1994,肖申克的救赎
09-06 14:25:11.308 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1291546,1993,霸王别姬
09-06 14:25:11.308 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1295644,1994,这个杀手不太冷
09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1292720,1994,阿甘正传
09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1292063,1997,美丽人生
09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1291561,2001,千与千寻
09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1295124,1993,辛德勒的名单
09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1292722,1997,泰坦尼克号
09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 3541415,2010,盗梦空间
09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 2131459,2008,机器人总动员
09-06 14:25:11.311 30008-30008/eee.rxjavaretrofit D/MainActivity: onComplete: Over!
这样就完成了最基本的RxJava+Retrofit,结果也与预期一致。
但是这样显然不能让我们去优雅的使用。毕竟优雅的复用已写过的代码才是我们的追求。
因此我们需要进一步对刚才的代码进行封装。
基本的封装RxJava+Retrofit
1.封装Retrofit请求过程
在之前的代码的基础上,创建Api.java用于封装Retrofit,代码如下:
public class Api {
public static String baseUrl = "https://api.douban.com/v2/movie/";
public static ApiService apiService;
//单例
public static ApiService getApiService() {
if (apiService == null) {
synchronized (Api.class) {
if (apiService == null) {
new Api();
}
}
}
return apiService;
}
private Api() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())//请求的结果转为实体类
//适配RxJava2.0,RxJava1.x则为RxJavaCallAdapterFactory.create()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
apiService = retrofit.create(ApiService.class);
}
}
2.封装线程管理和订阅
在使用RxJava的过程中,每次都重复写线程管理和订阅代码也是一件繁琐的事情。
创建ApiMethods用于封装线程管理和订阅的过程及调用请求。如下:
public class ApiMethods {
/**
* 封装线程管理和订阅的过程
*/
public static void ApiSubscribe(Observable observable, Observer observer) {
observable.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
/**
* 用于获取豆瓣电影Top250的数据
*
* @param observer 由调用者传过来的观察者对象
* @param start 起始位置
* @param count 获取长度
*/
public static void getTopMovie(Observer<Movie> observer, int start, int count) {
ApiSubscribe(Api.getApiService().getTopMovie(start, count), observer);
}
}
3.Activity中调用及处理数据
Observer<Movie> observer = new Observer<Movie>() {
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe: ");
}
@Override
public void onNext(Movie movie) {
Log.d(TAG, "onNext: " + movie.getTitle());
List<Subjects> list = movie.getSubjects();
for (Subjects sub : list) {
Log.d(TAG, "onNext: " + sub.getId() + "," + sub.getYear() + "," + sub.getTitle());
}
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete: Over!");
}
};
ApiMethods.getTopMovie(observer, 0, 10);
4.测试结果
09-06 15:14:01.413 16819-16819/eee.rxjavaretrofit D/MainActivity: onSubscribe:
09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 豆瓣电影Top250
09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1292052,1994,肖申克的救赎
09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1291546,1993,霸王别姬
09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1295644,1994,这个杀手不太冷
09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1292720,1994,阿甘正传
09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1292063,1997,美丽人生
09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1291561,2001,千与千寻
09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1295124,1993,辛德勒的名单
09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1292722,1997,泰坦尼克号
09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 3541415,2010,盗梦空间
09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 2131459,2008,机器人总动员
09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onComplete: Over!
经过两次封装,如果有新的Api接口我们只需要在ApiService类中增加对应的接口方法,然后在ApiMethods类中写对应的请求方法即可。与基本的RxJava+Retrofit相比,在额外代码量和易用性上都明显占优。
进一步封装RxJava+Retrofit
看完上一步,你还是肯定还是会觉得,在Activity每次处理订阅返回的数据的时候都要实例化一个Observer对象,然后重写onSubscribe,onNext,onError,onComplete四个方法。虽然Android Studio一键就能补全代码,我们只用简单处理一下就ok了。但是那么多代码堆在那里还是让人不爽的。
因此我们进一步针对Observer进行封装。
针对重写方法的封装最好的方法就是使用接口+implements/继承的方式实现。
仔细分析会发现,其实除了onNext涉及数据处理,其余三个方法都可以进行模板话处理,比如显示一个Toast,输出错误信息等;当然如果你的业务需要,也可以按照onNext特别处理。
在之前代码的基础上。
1.新建监听接口
使用通配泛型,适用于所有类型的数据。
public interface ObserverOnNextListener<T> {
void onNext(T t);
}
2.重写Observer
public class MyObserver<T> implements Observer<T> {
private static final String TAG = "MyObserver";
private ObserverOnNextListener listener;
private Context context;
public MyObserver(Context context, ObserverOnNextListener listener) {
this.listener = listener;
this.context = context;
}
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe: ");
//添加业务处理
}
@Override
public void onNext(T t) {
listener.onNext(t);
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ", e);
//添加业务处理
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete: ");
//添加业务处理
}
}
3.Activity中使用
ObserverOnNextListener<Movie> listener = new ObserverOnNextListener<Movie>() {
@Override
public void onNext(Movie movie) {
Log.d(TAG, "onNext: " + movie.getTitle());
List<Subjects> list = movie.getSubjects();
for (Subjects sub : list) {
Log.d(TAG, "onNext: " + sub.getId() + "," + sub.getYear() + "," + sub.getTitle());
}
}
};
ApiMethods.getTopMovie(new MyObserver<Movie>(this, listener), 0, 10);
4.测试结果
09-06 16:01:59.918 3487-3487/eee.rxjavaretrofit D/MyObserver: onSubscribe:
09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 豆瓣电影Top250
09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1292052,1994,肖申克的救赎
09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1291546,1993,霸王别姬
09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1295644,1994,这个杀手不太冷
09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1292720,1994,阿甘正传
09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1292063,1997,美丽人生
09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1291561,2001,千与千寻
09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1295124,1993,辛德勒的名单
09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1292722,1997,泰坦尼克号
09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 3541415,2010,盗梦空间
09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 2131459,2008,机器人总动员
09-06 16:02:00.718 3487-3487/eee.rxjavaretrofit D/MyObserver: onComplete:
优雅的封装RxJava+Retrofit
上面的代码可谓是封装的彻底了,但是还不够优雅。
如何优雅?
网络请求一般都是耗时的,我们需要在进行网络请求的时候有一个进度框,增加用户体验。
根据上一步,我们只需要将进度框show在onSubscribe中,然后在onError/onComplete里cancel掉就行了。如果用户取消关闭了进度框,那么也随之取消了当前的Http请求。
1.编写ProgressDialog类代码
Handler接收两个消息来控制显示Dialog还是关闭Dialog。
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;
}
}
}
此处安全按照参考文章来写的,当然你也可以直接ProgressDialog dialog = new ProgressDialog(context);
2.编写监听取消的接口类
public interface ProgressCancelListener {
void onCancelProgress();
}
3.在MyObserver中添加ProgressDialog
将上面ProgressDialog在Observer中初始化,并在相应的位置进行显示和隐藏。
public class ProgressObserver<T> implements Observer<T>, ProgressCancelListener {
private static final String TAG = "ProgressObserver";
private ObserverOnNextListener listener;
private ProgressDialogHandler mProgressDialogHandler;
private Context context;
private Disposable d;
public ProgressObserver(Context context, ObserverOnNextListener listener) {
this.listener = listener;
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 onSubscribe(Disposable d) {
this.d = d;
showProgressDialog();
}
@Override
public void onNext(T t) {
listener.onNext(t);
}
@Override
public void onError(Throwable e) {
dismissProgressDialog();
Log.e(TAG, "onError: ", e);
}
@Override
public void onComplete() {
dismissProgressDialog();
Log.d(TAG, "onComplete: ");
}
@Override
public void onCancelProgress() {
//如果处于订阅状态,则取消订阅
if (!d.isDisposed()) {
d.dispose();
}
}
}
5.Activity中使用
ObserverOnNextListener<Movie> listener = new ObserverOnNextListener<Movie>() {
@Override
public void onNext(Movie movie) {
Log.d(TAG, "onNext: " + movie.getTitle());
List<Subjects> list = movie.getSubjects();
for (Subjects sub : list) {
Log.d(TAG, "onNext: " + sub.getId() + "," + sub.getYear() + "," + sub.getTitle());
}
}
};
ApiMethods.getTopMovie(new ProgressObserver<Movie>(this, listener), 0, 10);
6.测试结果
09-06 16:32:05.135 6823-6823/eee.rxjavaretrofit D/ProgressObserver: onSubscribe:
09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 豆瓣电影Top250
09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1292052,1994,肖申克的救赎
09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1291546,1993,霸王别姬
09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1295644,1994,这个杀手不太冷
09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1292720,1994,阿甘正传
09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1292063,1997,美丽人生
09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1291561,2001,千与千寻
09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1295124,1993,辛德勒的名单
09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1292722,1997,泰坦尼克号
09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 3541415,2010,盗梦空间
09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 2131459,2008,机器人总动员
09-06 16:32:05.992 6823-6823/eee.rxjavaretrofit D/ProgressObserver: onComplete:
优化Retrofit,设置缓存,超时策略
使用OkHttpClient对Retrofit设置设置缓存,超时策略。
public class ApiStrategy {
public static String baseUrl = "https://api.douban.com/v2/movie/";
//读超时长,单位:毫秒
public static final int READ_TIME_OUT = 7676;
//连接时长,单位:毫秒
public static final int CONNECT_TIME_OUT = 7676;
public static ApiService apiService;
public static ApiService getApiService() {
if (apiService == null) {
synchronized (Api.class) {
if (apiService == null) {
new ApiStrategy();
}
}
}
return apiService;
}
private ApiStrategy() {
HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor();
logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
//缓存
File cacheFile = new File(MyApplication.getContext().getCacheDir(), "cache");
Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb
//增加头部信息
Interceptor headerInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request build = chain.request().newBuilder()
//.addHeader("Content-Type", "application/json")//设置允许请求json数据
.build();
return chain.proceed(build);
}
};
//创建一个OkHttpClient并设置超时时间
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(READ_TIME_OUT, TimeUnit.MILLISECONDS)
.connectTimeout(CONNECT_TIME_OUT, TimeUnit.MILLISECONDS)
.addInterceptor(mRewriteCacheControlInterceptor)
.addNetworkInterceptor(mRewriteCacheControlInterceptor)
.addInterceptor(headerInterceptor)
.addInterceptor(logInterceptor)
.cache(cache)
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())//请求的结果转为实体类
//适配RxJava2.0,RxJava1.x则为RxJavaCallAdapterFactory.create()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
apiService = retrofit.create(ApiService.class);
}
/**
* 设缓存有效期为两天
*/
private static final long CACHE_STALE_SEC = 60 * 60 * 24 * 2;
/**
* 云端响应头拦截器,用来配置缓存策略
* Dangerous interceptor that rewrites the server's cache-control header.
*/
private final Interceptor mRewriteCacheControlInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String cacheControl = request.cacheControl().toString();
if (!NetWorkUtils.isNetConnected(MyApplication.getContext())) {
request = request.newBuilder()
.cacheControl(TextUtils.isEmpty(cacheControl) ? CacheControl
.FORCE_NETWORK : CacheControl.FORCE_CACHE)
.build();
}
Response originalResponse = chain.proceed(request);
if (NetWorkUtils.isNetConnected(MyApplication.getContext())) {
return originalResponse.newBuilder()
.header("Cache-Control", cacheControl)
.removeHeader("Pragma")
.build();
} else {
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" +
CACHE_STALE_SEC)
.removeHeader("Pragma")
.build();
}
}
};
}
总结
目前为止,就封装完毕了。
现在我们再写一个新的网络请求,步骤是这样的:
- 在Service中定义一个新的方法。
- 在ApiMethods封装对应的请求(代码基本可以copy)
- 创建一个ObserverOnNextListener处理请求数据。
- 在onNext处理业务逻辑。
Module代码下载
PS:因为是Module的代码,所以请谨慎导入。
CSDN代码下载:http://download.csdn.net/download/demonliuhui/9966975
只看代码不下载可以GitHub:https://github.com/DeMonLiu623/DeMonTest/tree/master/RxJavaRetrofit