美文网首页
Retrofit与Rxjava的探索实践

Retrofit与Rxjava的探索实践

作者: DDstar | 来源:发表于2017-02-20 18:43 被阅读71次

    本文主要参考此篇文章力作,原文链接
    [给 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) {
            //错误时的处理
        }
    }
    

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



    相关文章

      网友评论

          本文标题:Retrofit与Rxjava的探索实践

          本文链接:https://www.haomeiwen.com/subject/juwswttx.html