本文主要参考此篇文章力作,原文链接
[给 Android 开发者的 RxJava 详解]
(http://gank.io/post/560e15be2dca930e00da1083#toc_3)
写在前面
最近在摸索着Rxjava,学了一大半,但是深知要实践与理论结合才能学得快也记得牢,然而最好的实践是什么呢?可能是我学得还不够深,觉得它的好处在网络请求这边特别明显,于是网络请求网络请求网络请求。。。
发现有做得更好的东西,那就是Retrofit与Rxjava这两个小情侣特别好,所以就再次看了一下这两个的实践,发现,真的有了一个新大陆~然后就没什么好说的了,搞起呗。
本次实践基于androidstudio,所以很多库的依赖都使用gradle来配置
实践1 库的安装
首先依赖rxjava
compile 'io.reactivex:rxjava:x.y.z'
compile 'io.reactivex:rxandroid:1.0.1'
接下来依赖retrofit
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'//使用Gson解析
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'//异常时的处理
实践2 处理场景
我们假设使用的场景是输入账号密码,请求网络进行账号验证,验证成功就直接登录,不过在登录之前需要获取到Token,根据Token和输入的账号密码进行登录验证。
所以我们需要两个方法,一个获取token,一个登录返回结果。
假设我们返回的数据结构是固定的,就像以下:
{
"code":0,
"message":"success",
"data":{
...
}
}
实践3 代码实现
- 首先有基础的Retrofit和rxjava的请求,这边叫RxReService
public interface RxReService {
@POST("user/login")
Observable<String> login(
@Query("token") String token,
@Query("username") String name,
@Query("password") String psd);
}
@POST("token")
Observable<String> getToken();
这是一个接口,通用标注的方式传入url,使用query方式添加参数
- 接口写好了,看一下实际的调用,叫RxReRequest
public class RxReRequest{
private static String BASEURL = "https://www.xxxx.com/";
private static RxReService mRxReService;
}
public static void initRxRe() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASEURL)
.build();
mRxReService = retrofit.create(RxReService.class);
}
public static Observable<String> getToken(){
return mRxReService.getToken();
}
public static Observable<String> login(String name,String psd,String token){
return mRxReService.login(token,name,psd);
}
其实就两个方法,getToken和login,这边是对其请求进行简单封装
- 简单使用
RxReRequestHelper.login("", "", new ProgressSubscribe<String>(new SubscriberOnNextListener<String>() {
@Override
public void onNext(String result) {
}
}, MainActivity.this));
RxReRequest.getToken().subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String userTokenResultData) {
//对String进行解析
...
//解析完得到toekn
String token = token.getToken();
RxReRequest.login("name","psd",token).subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String comResultResultData) {
//再次解析
...
}
});
}
});
一句我的天啊,日了狗,这代码。。。
莫急莫急,待我慢慢道来。
我们这边是用比较复杂的请求才能看出rxjava和retrofit的便利之处
实践4 封装
我们首先来说一下
首先是使用gson解析的话,retrofit已经做了很好的处理,只需在initRxRe这个地方添加一个参数
.addConverterFactory(GsonConverterFactory.create())//添加Gson解析库
顺便说一下这个
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加取消订阅时取消http请求
这个是当取消订阅时自动取消http请求
有了以上这些,我们就可以进行后面的工作了
4-1 请求结束自动解析
添加以上两个参数后,我们的RxReService就变成
Observable<ComResult> login(
@Query("token") String token,
@Query("username") String name,
@Query("password") String psd);
@POST("token")
Observable<UserToken> getToken();
返回的类型就直接转换成我们要的最终类型
RxReRequest的两个方法变成
public static Observable<UserToken> getToken(){
return mRxReService.getToken();
}
public static Observable<ComResult> login(String name, String psd, String token){
return mRxReService.login(token,name,psd);
}
因此最后的使用变成
RxReRequest.getToken().subscribe(new Subscriber<UserToken>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(UserToken userTokenResultData) {
RxReRequest.login("name","psd",userTokenResultData.getToken()).subscribe(new Subscriber<ComResult>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(ComResult comResultResultData) {
}
});
}
});
Gson解析省略掉了,不过看了还是有些别扭我们发现其实需要的就onErr和onNext两个方法而已,还有我们解析的会将整个返回值都每次解析出来,但是我们的返回格式是固定的呀,我每次只要根据code拿数据实例就好了,那就是提前预解析
4-2 提前预解析
再啰嗦一下,假设我们的返回结果是
{
"code":0,
"message":"success",
"data":{
...
}
}
那我们就可以每次先对结果进行解析,如果是code不等于0那就不解析了,所以我们需要这么一个类
public class ResultData<T> {
private int resultCode;
private String message;
private T data;
public int getResultCode() {
return resultCode;
}
public String getMessage() {
return message;
}
public T getData() {
return data;
}
}
因为这个实例数据是不固定的,所以只能用泛型来做,所以我们的RxReService又变了
@POST("user/login")
Observable<ResultData<ComResult>> login(
@Query("token") String token,
@Query("username") String name,
@Query("password") String psd);
跟着变的RxReRequest
public static Observable<ResultData<UserToken>> getToken() {
return mRxReService.getToken();
}
public static Observable<ResultData<ComResult>> login(String name, String psd, String token) {
return mRxReService.login(token, name, psd);
}
我们在哪里进行预解析呢?当然是用rxjava牛逼闪闪的map关键字了。我们预解析的目的是当code不为0调用onErr方法,而不仅仅是访问出错,这样我们在onNext那边只需关心正确的数据就是了
我们知道map的参数
public final <R> Observable<R> map(Func1<? super T, ? extends R> func) {
return lift(new OperatorMap<T, R>(func));
}
所以我们需要定义一个func1来继承这个Func1
public class HttpResultFunc<T> implements Func1<ResultData<T>, T> {
@Override
public T call(ResultData<T> tResultData) {
if (tResultData.getResultCode() != 0) {
throw new ResultException(tResultData.getResultCode(), tResultData.getMessage());
}
return tResultData.getData();
}
}
我们怎么做的呢?就是当code不等于0就抛出一个异常,让onErr接收到这个异常,但是这个异常又要包含到数据异常的信息,所以我们还需要自己定义一个异常
public class ResultException extends RuntimeException {
private int errorCode;
private String errMessage;
public ResultException(int errorCode, String errMessage) {
this.errorCode = errorCode;
this.errMessage = errMessage;
}
public int getErrorCode() {
return errorCode;
}
public String getErrMessage() {
return errMessage;
}
}
里面包含了errCode和errMessage
好这边我们改变一下RxReRequest
public static void getToken(Subscriber<UserToken> subscriber) {
mRxReService.getToken().map(new HttpResultFunc<UserToken>()).subscribe(subscriber);
}
public static void login(String name, String psd, String token, Subscriber<ComResult> subscriber) {
mRxReService.login(token, name, psd).map(new HttpResultFunc<ComResult>()).subscribe(subscriber);
}
我们使用了map对其进行预解析
好的,我们看看怎么使用
RxReRequest.getToken(new Subscriber<UserToken>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(UserToken userToken) {
RxReRequest.login("", "", userToken.getToken(), new Subscriber<ComResult>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(ComResult comResult) {
}
});
}
});
这边onNext里面都是实在的数据,不会再有数据为空时会跑进去了
还有一个小东西,我们发现onComplete是没用的,那我们也给他去掉吧~~怎么做,自己定义咯
4-3 去掉onComplete
我们自定义一个观察者,也是抽象类
public abstract class ResutSubscriber<T> extends Subscriber<T>{
@Override
public void onCompleted() {
//结束
}
}
当然继承免不了,这边可以做个打印啊还是啥的,当然如果这个有用到,就不能这样做啦
好的,跟着改变的是这边RxReRequest
public static void getToken(ResutSubscriber<UserToken> subscriber) {
mRxReService.getToken().map(new HttpResultFunc<UserToken>()).subscribe(subscriber);
}
public static void login(String name, String psd, String token, ResutSubscriber<ComResult> subscriber) {
mRxReService.login(token, name, psd).map(new HttpResultFunc<ComResult>()).subscribe(subscriber);
}
这样使用的话就简便了一点点
RxReRequest.getToken(new ResutSubscriber<UserToken>() {
@Override
public void onError(Throwable e) {
if (e instanceof ResultException){
Log.e("err",((ResultException)e).getErrMessage() +((ResultException)e).getErrorCode() );
}else {
Log.e("err","请求异常");
}
}
@Override
public void onNext(UserToken userToken) {
RxReRequest.login("", "", userToken.getToken(), new ResutSubscriber<ComResult>() {
@Override
public void onError(Throwable e) {
if (e instanceof ResultException){
Log.e("err",((ResultException)e).getErrMessage() +((ResultException)e).getErrorCode() );
}else {
Log.e("err","请求异常");
}
}
@Override
public void onNext(ComResult comResult) {
}
});
}
});
这样没用的代码就没掉了,不过这个嵌套的网络请求看了总是不开心,怎么办呢。。。我们知道rxjava还有flatMap,那就用上吧。
4-4使用flatmap处理需要两级请求的情况
flatmap的解释很不好说很不好说也不知道怎么说,在这个场景具体的我们可以理解为,请求登录的话会先要求获取token,获取到了在执行登录的请求,那就上代码吧。
我们把两个方法合成一个方法
于是RxReRequest的登录方法里面要做两件事,一件是获取token,一件是登录,所以
public static void login(final String name, final String psd, final ResutSubscriber<ComResult> subscriber) {
mRxReService.getToken().map(new HttpResultFunc<UserToken>()).flatMap(new Func1<UserToken, Observable<ComResult>>() {
@Override
public Observable<ComResult> call(UserToken userToken) {
return mRxReService.login(userToken.getToken(), name, psd).map(new HttpResultFunc<ComResult>());
}
}).subscribe(subscriber);
}
最后的使用
RxReRequest.login("", "", new ResutSubscriber<ComResult>() {
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(ComResult comResult) {
}
});
这下,整个世界都清净了~~~~
4-5 线程切换
我们知道安卓是不能在主线程进行耗时操作的,包括网络请求,所以我们如果按上面的来做的话分分钟抛异常,所以还需要一把杀手锏,就是线程切换。
使用Rxjava可以很方便进行线程切换,当进行网络请求时,线程切换到子线程(另外新建一个线程),请求结束后切换到主线程
RxReRequest里面的请求
public static void getToken(ResutSubscriber<UserToken> subscriber) {
mRxReService.getToken().map(new HttpResultFunc<UserToken>()).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).subscribe(subscriber);
}
public static void login(final String name, final String psd, final ResutSubscriber<ComResult> subscriber) {
mRxReService.getToken().map(new HttpResultFunc<UserToken>()).flatMap(new Func1<UserToken, Observable<ComResult>>() {
@Override
public Observable<ComResult> call(UserToken userToken) {
return mRxReService.login(userToken.getToken(), name, psd).map(new HttpResultFunc<ComResult>());
}
}).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).subscribe(subscriber);
}
subscribeOn表示事件发生时所在的线程,这边指定在新建的线程,observeOn则指定观察者发生的事件的线程,我们指定在主线程。
当需要loading的时候
因为我们网络请求的观察者都发生在主线程,所以我们还是自己定义一个观察者,里面包含开始和结束,在开始的地方显示dialog,在结束或者出错的地方做响应的取消dialog或者显示错误信息,还可以根据出错的errcode进行灵活展示
public abstract class ProgressSubscribe<T> extends Subscriber<T> {
private Context mContext;
private Handler mHandler;
public ProgressSubscribe(Context mContext) {
this.mContext = mContext;
mHandler = new Handler();
}
@Override
public void onCompleted() {
//dismissDialog
}
@Override
public void onStart() {
//showDialog
}\ @Override
public void onError(Throwable e) {
//错误时的处理
}
}
这样错误的进行统一处理,我们就只要关注有数据的业务逻辑就行了。。。。
完