Retrofit与Rxjava的探索实践

2017-02-20  本文已影响71人  DDstar

本文主要参考此篇文章力作,原文链接
[给 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 代码实现

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方式添加参数

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) {
        //错误时的处理
    }
}

这样错误的进行统一处理,我们就只要关注有数据的业务逻辑就行了。。。。



上一篇下一篇

猜你喜欢

热点阅读