Retrofit与Rxjava的探索实践

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

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



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 前言我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard...
    占导zqq阅读 9,158评论 6 151
  • 我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard 的...
    Jason_andy阅读 5,451评论 7 62
  • 文章转自:http://gank.io/post/560e15be2dca930e00da1083作者:扔物线在正...
    xpengb阅读 7,017评论 9 73
  • 最近项目里面有用到Rxjava框架,感觉很强大的巨作,所以在网上搜了很多相关文章,发现一片文章很不错,今天把这篇文...
    Scus阅读 6,853评论 2 50
  • 啊啊啊!怎么一晃,一年多就过去了?! 好,那就让我想想这一年来的变化! 这一年,前文日记中所写的那个‘他‘,我已安...
    雨润新荷阅读 224评论 0 0