此文旨在讲解RxJava+Retrofit联网请求的结合应用,我将尽我所能的详细讲解。文章的末尾将源码奉上
代码可实现如下功能:
1.网络请求带缓存
2.可取消网络请求
3.加载时显示圆形进度条,加载完毕后移除
4.简单的封装
纳尼!~你对RxJava 和 Retrofit 不甚了解,那么......
RxJava
RxJava学习是一个曲折漫长的过程,但一旦掌握,妙用无穷。
1.给 Android 开发者的 RxJava 详解
2.关于RxJava最友好的文章
3.关于RxJava最友好的文章(进阶)
Retrofit
1.Retrofit用法详解
2.Retrofit 2.0:有史以来最大的改进
3.Retrofit 2.0 官方文档
4. Retrofit2 完全解析 探索与okhttp之间的关系
网络接口的使用为查询天气的接口
http://wthrcdn.etouch.cn/weather_mini?city=北京
返回的Json格式我就不详细介绍了,需要的转换下格式自行看一下
关于返回的结果我们封装成实体类 WeatherEntity
1.1添加所诉依赖build.gradle
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
<uses-permission android:name="android.permission.INTERNET" />
1.2 Get请求
public interface GetWeatherService {
@GET("weather_mini")
Call getMessage(@Query("city") String city);
}
在MianActivity中 我们写在initOnlyRetrofit()方法中,如下:
/**
* 只有Retrofit的联网请求
*/
private void initOnlyRetrofit() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://wthrcdn.etouch.cn/")//基础URL 建议以 / 结尾
.addConverterFactory(GsonConverterFactory.create())//设置 Json 转换器
.build();
GetWeatherService weatherService = retrofit.create(GetWeatherService.class);
Call call = weatherService.getMessage("北京");
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
Log.e(TAG, "response == " + response.body().getData().getGanmao());
}
@Override
public void onFailure(Call call, Throwable t) {
Log.e(TAG, "Throwable : " + t);
}
});
}
运行一下我们可以看到打印结果如下:
2.1 同样先添加依赖(新增的)
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'io.reactivex:rxandroid:1.2.1'
compile 'io.reactivex:rxjava:1.2.1'
2.2 这时将Retrofit与RxJava 链接起来只需这样修改.
在Retrofit Builder链表中如下调用addCallAdapterFactory
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://wthrcdn.etouch.cn/")//基础URL 建议以 / 结尾
.addConverterFactory(GsonConverterFactory.create())//设置 Json 转换器
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//RxJava 适配器
.build();
2.3 这样一来我们定义的service返回值就不在是一个Call了,而是一个Observable ,那我们需重新定义接口
public interface GetWeatherService {
@GET("weather_mini")
Observable getRxMessage(@Query("city") String city);
}
2.4 重新定以后你的Service接口现在可以作为Observable返回了.我们需要用RxJava方式实现网络请求,可以完全像RxJava那样使用它.
GetWeatherService weatherService = retrofit.create(GetWeatherService.class);
weatherService.getRxMessage("北京")
.subscribeOn(Schedulers.io())//IO线程加载数据
.observeOn(AndroidSchedulers.mainThread())//主线程显示数据
.subscribe(new Subscriber() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(WeatherEntity weatherEntity) {
Log.e(TAG,"RxJava + Retrofit= " + weatherEntity.getData().getGanmao());
}
});
subscribeOn(): 指定 subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。
observeOn(): 指定 Subscriber 所运行在的线程。或者叫做事件消费的线程。
2.5 这里实现基本的网络请求了,我们来看下打印日志.
嗯,联网实现了,我看可以考虑收工啦~~~ 纳尼? 说好的缓存呢?dialog呢?还有取消请求我该怎么做? 嗯,嗯,别捉急,我开个玩笑,马上带来重头戏.
在Retrofit 2.0中,OkHttp 是必须的,并且自动设置为了依赖。也就说我们不用在build.gradle添加依赖了。下面的代码是从Retrofit 2.0的pom文件中抓取的。
<dependencies>
<dependency>
<groupId>com.squareup.okhttpgroupId>
<artifactId>okhttpartifactId>
dependency>
...
dependencies>
为了让OkHttp 的Call模式成为可能,在Retrofit 2.0中OkHttp 自动被用作HTTP 接口。此时我们要在OkHttpClient里设置缓存,添加拦截器.那OkHttpClient在Retrofit里怎么设置呢.其实只需添加一句,在retrofit链式调用中调用client(OkHttpClient client)方法即可,来看下代码:
OkHttpClient mClient = new OkHttpClient.Builder()
.addInterceptor(mInterceptor)//应用程序拦截器
.addNetworkInterceptor(mNetInterceptor)//网络拦截器
.cache(mCache)//添加缓存
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.client(mClient)//添加OK
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
OkHttpClient我们分别添加了3方法.
.cache(mCache)用于添加缓存;
.addInterceptor(mInterceptor)应用程序拦截器;
.addNetworkInterceptor(mNetInterceptor)网络拦截器;
3.1 OkHttp本身是有缓存这个东西的,只是如果你不去设置,是不起作用的 我们需要设置.cache()去添加缓存
File mFile = new File(context.getCacheDir() + "http");//储存目录
long maxSize = 10 * 1024 * 1024; // 10 MB 最大缓存数
Cache mCache = new Cache(mFile, maxSize);
3.2 这里设置了缓存和目录还是不够的,我们还需要设置拦截器Interceptors
先来看看Interceptor本身的文档解释:观察,修改以及可能短路的请求输出和响应请求的回来。通常情况下拦截器用来添加,移除或者转换请求或者回应的头部信息。 拦截器接口中有intercept(Chain chain)方法,同时返回Response。
chain.proceed(request)是拦截器的关键部分。这个看似简单的方法是所有的HTTP工作发生的地方,产生满足要求的反应。 拦截器可以链接。假设你有一个压缩的拦截和校验拦截器:你需要决定数据是否被压缩,或者校验或校验然后压缩。okhttp使用列表来跟踪和拦截,拦截器会按顺序调用。
3.3 拦截器可以注册为应用程序拦截或网络拦截
每种拦截器chain有相对的优势。
Application interceptors(应用拦截器)
- 1.不必担心中间的responses,例如重定向和重连。
- 2.总是调用一次,即使是从缓存HTTP响应。
- 3.观察应用程序的原始意图。不关心OkHttp的注入headers,例如If-None-Match
- 4.允许短路和不执行Chain.proceed().
- 5.允许重连,多次调用proceed()。
.
Network Interceptors (网络拦截器)
- 1.能够操作中间反应,例如重定向和重连。
- 2.不能被缓存响应,例如短路网络调用。
- 3.观察数据,正如它将在网络上传输。
- 4.有权使用携带request的Connection
具体2种选择器用哪种大家酌情考虑即可。说了这么多,拦截Interceptor到底该怎么写?下面个给出拦截器的代码片段。
private static final int NET_MAX = 30; //30秒 有网超时时间
private static final int NO_NET_MAX = 60 * 60 * 24 * 7; //7天 无网超时时间
//网络拦截器
Interceptor mNetInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Log.e("TAG", "拦截 应用 缓存");
Request request = chain.request();
if (!NetWorkUtils.networkIsAvailable(context)) {//判断网络状态 无网络时
request = request.newBuilder()
//Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。为了确保缓存生效
.removeHeader("Pragma")
.header("Cache-Control", "private, only-if-cached, max-stale=" + NO_NET_MAX)
.build();
} else {
request = request.newBuilder()
//Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。为了确保缓存生效
.removeHeader("Pragma")
.header("Cache-Control", "private, max-age=" + NET_MAX)//添加缓存请求头
.build();
}
return chain.proceed(request);
}
};
这里NetWorkUtils.networkIsAvailable(context)是判断网络状态的工具类,大家可以去源码里找,这里就不在给出.
3.4 Okhttp的缓存由返回的header 来决定,用”Cache-control”来控制,常见的取值有private、no-cache、max-age、must-revalidate等,默认为private。
- 在某些情况下,如用户单击“刷新”按钮,就可能有必要跳过缓存,并直接从服务器获取数据。要强制刷新,添加无缓存指令:"Cache-Control": "no-cache"。
- 如果缓存只是用来和服务器做验证,可是设置更有效的"Cache-Control":"max-age=0"。
- 有时你会想显示可以立即显示的资源。这是可以使用的,这样你的应用程序可以在等待最新的数据下载的时候显示一些东西, 重定向request到本地缓存资源,添加"Cache-Control":"only-if-cached"。
- 有时候过期的response比没有response更好,设置最长过期时间来允许过期的response响应:int maxStale = 30; // 30秒 "Cache-Control":"max-stale=" + maxStale。
缓存的实现到这里就讲完了,可能代码片段让你看的头晕目眩,那给大家一个简单封装了的完整代码
package com.aaron.rxjava_retrofit.net;
import android.content.Context;
import android.util.Log;
import com.aaron.rxjava_retrofit.utils.NetWorkUtils;
import java.io.File;
import java.io.IOException;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* 作者:哇牛Aaron
* 作者简书文章地址: http://www.jianshu.com/users/07a8b5386866/latest_articles
* 时间: 2016/11/24
* 功能描述:
*/
public class HttpUtils {
private static final String URL_WEATHER = "http://wthrcdn.etouch.cn/";
private static final int NET_MAX = 30; //30秒 有网超时时间
private static final int NO_NET_MAX = 60 * 60 * 24 * 7; //7天 无网超时时间
public static Retrofit getRetrofit(String url, final Context context) {
//应用程序拦截器
Interceptor mInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Log.e("TAG", "拦截 网络 缓存");
Request request = chain.request();
if (!NetWorkUtils.networkIsAvailable(context)) {//判断网络状态 无网络时
Log.e("TAG", "无网~~ 缓存");
request = request.newBuilder()
//Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。为了确保缓存生效
.removeHeader("Pragma")
.header("Cache-Control", "private, only-if-cached, max-stale=" + NO_NET_MAX)
.build();
} else {//有网状态
Log.e("TAG", "有网~~ 缓存");
request = request.newBuilder()
//Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。为了确保缓存生效
.removeHeader("Pragma")
.header("Cache-Control", "private, max-age=" + NET_MAX)//添加缓存请求头
.build();
}
return chain.proceed(request);
}
};
//网络拦截器
Interceptor mNetInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Log.e("TAG", "拦截 应用 缓存");
Request request = chain.request();
if (!NetWorkUtils.networkIsAvailable(context)) {//判断网络状态 无网络时
request = request.newBuilder()
//Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。为了确保缓存生效
.removeHeader("Pragma")
.header("Cache-Control", "private, only-if-cached, max-stale=" + NO_NET_MAX)
.build();
} else {
request = request.newBuilder()
//Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。为了确保缓存生效
.removeHeader("Pragma")
.header("Cache-Control", "private, max-age=" + NET_MAX)//添加缓存请求头
.build();
}
return chain.proceed(request);
}
};
File mFile = new File(context.getCacheDir() + "http");//储存目录
long maxSize = 10 * 1024 * 1024; // 10 MB 最大缓存数
Cache mCache = new Cache(mFile, maxSize);
OkHttpClient mClient = new OkHttpClient.Builder()
.addInterceptor(mInterceptor)//应用程序拦截器
.addNetworkInterceptor(mNetInterceptor)//网络拦截器
.cache(mCache)//添加缓存
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.client(mClient)//添加OK
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
return retrofit;
}
public static GetWeatherService createWeatherService(Context context) {
return getRetrofit(URL_WEATHER, context).create(GetWeatherService.class);
}
}
在MainActivity中调用
HttpUtils.createWeatherService(MainActivity.this).getRxMessage("北京")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(WeatherEntity weatherEntity) {
Log.e(TAG, "Cache RxJava + Retrofit= " + weatherEntity.getData().getGanmao());
}
});
此时,缓存已经完成了.~~~嗯,写的我有点累了.
学习关于OkHttp Interceptor的知识,请到OkHttp Interceptors。
当Activity结束后我们需要结束网络请求,为用户节省流量,或者其它需求等
Retrofit中你只需调用call.cancel()。就可以取消网络请求 可是前面已经说了 Call 此时 已经变成了 Observable.那么如何取消呢?我们能做的似乎只有解除对Observable对象的订阅,其他的什么也做不了。
好在Retrofit已经帮我们考虑到了这一点。 答案在RxJavaCallAdapterFactory这个类的源码中可以找到。
static final class CallOnSubscribe<T> implements Observable.OnSubscribe<Response<T>> {
private final Call originalCall;
CallOnSubscribe(Call originalCall) {
this.originalCall = originalCall;
}
@Override public void call(final Subscriber super Response> subscriber) {
// Since Call is a one-shot type, clone it for each new subscriber.
final Call call = originalCall.clone();
// Attempt to cancel the call if it is still in-flight on unsubscription.
subscriber.add(Subscriptions.create(new Action0() {
@Override public void call() {
call.cancel();
}
}));
try {
Response response = call.execute();
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(response);
}
} catch (Throwable t) {
Exceptions.throwIfFatal(t);
if (!subscriber.isUnsubscribed()) {
subscriber.onError(t);
}
return;
}
if (!subscriber.isUnsubscribed()) {
subscriber.onCompleted();
}
}
}
我们看到call方法中,给subscriber添加了一个Subscription对象,Subscription对象很简单,主要就是取消订阅用的,如果你查看Subscriptions.create的源码,发现是这样的。
public static Subscription create(final Action0 unsubscribe) {
return BooleanSubscription.create(unsubscribe);
}
利用了一个BooleanSubscription类来创建一个Subscription,如果你点进去看BooleanSubscription.create方法一切就清晰了,当接触绑定的时候,subscriber会调用Subscription的unsubscribe方法,然后触发创建Subscription时候的传递进来的Action0的call方法。RxJavaCallAdapterFactory帮我们给subscriber添加的是call.cancel()。
Subscription subscription = HttpUtils.createWeatherService(MainActivity.this).getRxMessage("北京")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(WeatherEntity weatherEntity) {
Log.e(TAG, "Cache RxJava + Retrofit= " + weatherEntity.getData().getGanmao());
}
});
/*if (subscription != null && !subscription.isUnsubscribed()) {//isUnsubscribed 是否取消订阅
subscription.unsubscribe();//取消网络请求
}*/
要添加dialog 我们不得不提RxJava中的两个观察者Observer 和 Subscriber 你只想使用基本功能,这两个观察者基本是一致的它们的区别对于使用者来说主要有两点:
2.unsubscribe(): 这是 Subscriber 所实现的另一个接口 Subscription 的方法,用于取消订阅。在这个方法被调用后,Subscriber 将不再接收事件。一般在这个方法调用前,可以使用 isUnsubscribed() 先判断一下状态。 unsubscribe() 这个方法很重要,因为在 subscribe() 之后, Observable 会持有 Subscriber 的引用,这个引用如果不能及时被释放,将有内存泄露的风险。所以最好保持一个原则:要在不再使用的时候尽快在合适的地方(例如 onPause() onStop() 等方法中)调用 unsubscribe() 来解除引用关系,以避免内存泄露的发生。
final ProgressDialog mDialog = new ProgressDialog(this);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://wthrcdn.etouch.cn/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//RxJava 适配器
.build();
GetWeatherService weatherService = retrofit.create(GetWeatherService.class);
weatherService.getRxMessage("北京")
.subscribeOn(Schedulers.io())//指定网络请求在IO线程
.doOnSubscribe(new Action0() {
@Override
public void call() {
mDialog.show();
}
})
.subscribeOn(AndroidSchedulers.mainThread())//显示Dialog在主线程中
.observeOn(AndroidSchedulers.mainThread())//显示数据在主线程
.subscribe(new Subscriber() {
@Override
public void onCompleted() {
if (mDialog != null){
mDialog.cancel();
}
}
@Override
public void onError(Throwable e) {
if (mDialog != null){
mDialog.cancel();
}
}
@Override
public void onNext(WeatherEntity weatherEntity) {
Log.e(TAG, "RxJava + Retrofit= " + weatherEntity.getData().getGanmao());
}
});
如上,在 doOnSubscribe()的后面跟一个 subscribeOn(),就能指定准备工作的线程了。
好,终于讲完了,喜欢的小伙伴希望不要吝惜点个喜欢吧~
要转载的朋友,请注明出处,珍惜下我的劳动成果,谢谢。
有什么问题,欢迎大家留言 讨论。
源码点这里
补充:关于封装可以利用单例设计模式进行封装,这里给出一个简单的案例,源码就不再修改了。
/**
* 作者:哇牛Aaron
* 作者简书文章地址: http://www.jianshu.com/users/07a8b5386866/latest_articles
* 时间: 2016/11/28
* 功能描述:
*/
public class HttpUtils {
public static final String BASE_URL = "http://wthrcdn.etouch.cn/";
private Retrofit retrofit;
private static final int NO_NET_MAX = 60 * 60 * 24 * 7; //7天 无网超时时间
private static final int NET_MAX = 30; //30秒 有网超时时间
private HttpUtils() {
Interceptor mInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetWorkUtils.networkIsAvailable(AaronApplication.getObjectContext())) {
request = request.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "private, only-if-cached, max-stale=" + NO_NET_MAX)
.build();
} else {
request = request.newBuilder()
//Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。为了确保缓存生效
.removeHeader("Pragma")
.header("Cache-Control", "private, max-age=" + NET_MAX)//添加缓存请求头
.build();
}
return chain.proceed(request);
}
};
OkHttpClient mClient = new OkHttpClient.Builder()
.addNetworkInterceptor(mInterceptor)
.cache(new Cache(new File(AaronApplication.getObjectContext().getCacheDir() + "http")
, 1024 * 1024 * 10))
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(mClient)
.build();
}
//单例设计模式
private static class singRetrofit {
private static final HttpUtils instance = new HttpUtils();
}
public static HttpUtils getInstance() {
return singRetrofit.instance;
}
public static GetWeatherService createWeather() {
//GetWeatherService getWeatherService = getInstance().retrofit.create(GetWeatherService.class);
return getInstance().retrofit.create(GetWeatherService.class);
}
}