美文网首页Net安卓Android Knowledge
【Android】RxJava + Retrofit完成网络请求

【Android】RxJava + Retrofit完成网络请求

作者: 带心情去旅行 | 来源:发表于2016-05-23 01:25 被阅读53555次
RxJava + Retrofit

前言

本文基于RxJava、Retrofit的使用,若是对RxJava或Retrofit还不了解的简友可以先了解RxJava、Retrofit的用法再来看这篇文章。
在这片文章之前分别单独介绍过Rxjava以及Retrofit的使用:
Android Retrofit 2.0 的使用
Android RxJava的使用(一)基本用法
(以及后面的几篇,就不一一列出了)

使用

在了解了RxJava和Retrofit分别的用法后,RxJava、Retrofit的搭配使用也就不再话下了。
先看看使用Retrofit完成一次网络请求是怎样的

  • 单独使用Retrofit
    1、先写一个service
interface MyService {
    @GET("user/login" )
    Call<UserInfo> login(
            @Query("username") String username,
            @Query("password") String password
    );
}

2、获取Call执行网络请求

        Retrofit retrofit = new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(BASE_URL)
                .build();
        MyService service = retrofit.create(MyService.class);

        Call<UserInfo> call = service.login("1111", "ssss");
        call.enqueue(new Callback<UserInfo>() {
            @Override
            public void onResponse(Call<UserInfo> call, Response<UserInfo> response) {
                //请求成功操作
            }
            @Override
            public void onFailure(Call<UserInfo> call, Throwable t) {
                //请求失败操作
            }
        });

以上是Retrofit单独使用时的做法。那Retrofit与RxJava结合是怎样使用的?下面就来说说这篇文章的重点。

  • RxJava + Retrofit完成网络请求
    1、添加依赖。前四个分别是RxJava、RxAndroid、Retrofit以及Gson的库,最后那个才是新加入的,RxJava + Retrofit的使用需要用到最后那个包。
    compile 'io.reactivex:rxjava:x.y.z'
    compile 'io.reactivex:rxandroid:1.0.1'
    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    compile 'com.squareup.retrofit2:converter-gson:2.0.2'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'

注意:最后三个包的版本号必须一样,这里用的是2.0.2。
2、写一个登录的service

interface MyService {
    @GET("user/login" )
    Observable<UserInfo> login(
            @Query("username") String username,
            @Query("password") String password
    );
}

相比之前的service,这里getNews方法的返回值是Observable类型。Observable...是不是觉得很熟悉,这货不就是之前在RxJava使用到的被监听者?
3、使用Observable完成一个网络请求,登录成功后保存数据到本地。

        Retrofit retrofit = new Retrofit.Builder()
              .addConverterFactory(GsonConverterFactory.create())
              .addCallAdapterFactory(RxJavaCallAdapterFactory.create())//新的配置
              .baseUrl(BASE_URL)
              .build();
        MyService service = retrofit.create(MyService.class);

        service.login(phone, password)               //获取Observable对象
                .subscribeOn(Schedulers.newThread())//请求在新的线程中执行
                .observeOn(Schedulers.io())         //请求完成后在io线程中执行
                .doOnNext(new Action1<UserInfo>() {
                    @Override
                    public void call(UserInfo userInfo) {
                        saveUserInfo(userInfo);//保存用户信息到本地
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())//最后在主线程中执行
                .subscribe(new Subscriber<UserInfo>() {
                    @Override
                    public void onCompleted() {
                        
                    }

                    @Override
                    public void onError(Throwable e) {
                        //请求失败
                    }

                    @Override
                    public void onNext(UserInfo userInfo) {
                        //请求成功
                    }
                });

RxJava + Retrofit 形式的时候,Retrofit 把请求封装进 Observable ,在请求结束后调用 onNext() 或在请求失败后调用 onError()。
可以看到,调用了service的login方法后得到Observable对象,在新的线程中执行网络请求,请求成功后切换到io线程执行保存用户信息的动作,最后再切换到主线程执行请求失败onError()、请求成功onNext()。整体的逻辑十分清晰都在一条链中,就算还有别的要求还可以往里面添加,丝毫不影响代码的简洁。(终于举了一个有实际意义的例子)

注意:retrofit的初始化加了一行代码

addCallAdapterFactory(RxJavaCallAdapterFactory.create())
  • RxJava + Retrofit 进阶
    在上面举到登录后保存用户信息的例子,其实在做项目的时候,往往在登录后得到的并不是用户信息。一般登录后会得到token,然后根据token去获取用户的信息。他们的步骤是这样的:
    1、登录
    2、获取用户信息(前提:登录成功)
    可以看得出来,这是一个嵌套的结构...嵌套啊!!!天呐,最怕嵌套的结构了。
    使用RxJava + Retrofit来完成这样的请求(借用抛物线的例子,稍微做了点改动)
 //登录,获取token
@GET("/login")
public Observable<String> login(   
    @Query("username") String username,
    @Query("password") String password);
 //根据token获取用户信息
@GET("/user")
public Observable<User> getUser(
    @Query("token") String token);
//..................................
service.login("11111", "22222")
    .flatMap(new Func1<String, Observable<User>>() {  //得到token后获取用户信息
        @Override
        public Observable<User> onNext(String token) {
            return service.getUser(token);
        })
    .subscribeOn(Schedulers.newThread())//请求在新的线程中执行请求
    .observeOn(Schedulers.io())         //请求完成后在io线程中执行
    .doOnNext(new Action1<User>() {      //保存用户信息到本地
         @Override
         public void call(User userInfo) {
             saveUserInfo(userInfo);
         }
     })
    .observeOn(AndroidSchedulers.mainThread())//在主线程中执行
    .subscribe(new Observer<User>() {
        @Override
        public void onNext(User user) {
            //完成一次完整的登录请求
            userView.setUser(user);
        }

        @Override
        public void onCompleted() {
  
        }

        @Override
        public void onError(Throwable error) {
            //请求失败
        }
    });

通过一个flatMap()轻松完成一次嵌套的请求,而且逻辑十分清晰。so easy~~~

小结

RxJava的实用性从上面的两个例子慢慢体现了出来,逻辑越是复杂,RxJava的优势就越明显。RxJava的使用就暂时介绍到这里吧,使用过程中遇到好用的再出来跟大家分享。

以上有错误之处感谢指出

参考:给 Android 开发者的 RxJava 详解
(本文部分内容引用自该博客)

相关文章

网友评论

  • GoBg:问个问题,这样把Retrofit 和 Rxjava结合之后怎么看http的response
    GoBg:@带心情去旅行 感谢: :pray:
    带心情去旅行:@GoBg 这个我倒没试过,去查了下,你要的答案应该是这个https://blog.csdn.net/qxs965266509/article/details/73819910
  • Jay_Lwp:博主,一般登录后会得到token,然后根据token去获取用户的信息。这句话怎么理解?登陆后得到的是cookie吧?
    带心情去旅行:@Jay_Lwp 可以这边理解吧
  • 童话镇里蜿蜒的河:写的不错。解答了我的疑惑。厉害了,我的希😇
  • 76f76c02a29b:学习了,感谢楼主提供的资料
  • 花果山来的猴子:哥们,你在编辑文章的时候这些代码块是怎么放上去的
    带心情去旅行:@花果山来的猴子 或者在网页版简书的设置,好像有个编辑方式,markdown旁边有个问号,点进去也有
    花果山来的猴子:@带心情去旅行 好 我回头看看 谢谢啊
    带心情去旅行:@花果山来的猴子 查markdown语法,你就能找到了
  • 吕不懂大人:咦,刚好昨天早上在公众号看到这篇文章:relaxed:
    带心情去旅行:@吕不懂 哪个公众号?
  • ImTudou:大兄弟,可以的。
  • J_Leo:为什么.subscribeOn(Schedulers.newThread())//请求在新的线程中执行请求 这个一直报错啊?
    J_Leo:@带心情去旅行 是我粗心了,找到问题了.:no_mouth:
    带心情去旅行:@J_Leo 能把报错发出来吗
  • 龙儿筝:如何区分登录失败和获取用户信息失败呢?
    带心情去旅行:@龙儿筝 是的
    龙儿筝:@带心情去旅行 加标签感觉不优雅
    带心情去旅行:@龙儿筝 由于一旦请求失败,就会进入onError,暂时无法确定是哪个请求。不过你可以通过变量来判断
  • 菜小东:作者你好,看我之后我的感想就是使用RxJava可以使那些连续嵌套的处理逻辑变得很清晰,完全就是一条链子下来!但是我有一个问题,假设这样的连续请求场景(一环扣一环的请求,需要拿到前面请求的结果才能进行下一个请求):A请求--->B请求---->C请求----->D请求,如果在B请求这里出错了,但是A请求已经成功拿到数据了,如果有重新请求的功能(点击刷新),用这样的RxJava又得重头到尾开始,但是实际上A请求已经成功拿到下一个B请求的数据了,重新开始又会进行去进行A请求,从逻辑上讲A请求已经是多余的了!如果再C请求出错那重新请求也应该是从C开始,跳过A,B请求!那这样是不是需要在OnError里面判定到底是哪步出错了(其实我也不知道怎么判断 囧),然后还要写多从B开始的请求,从C开始的请求?然后中间请求到的数据也要保存下来,点击刷新才可以跳过某些不必要的请求,不知道作者有没有好的想法?
    带心情去旅行:@菜小东 如果是这种情况,使用用RxJava+Retrofit反而显得有点多余了。
    不过,网络请求本身就是不稳定的,服务端应该避免让客户端多次发起请求(能不发尽量不发)。如果你的应用存在连续多次请求的情况,这是不是设计存在问题?
    带心情去旅行:@菜小东 你的假设A请求--->B请求---->C请求----->D请求,应该是不会存在的。如果真有这样的情况,那这接口设计的有问题。
    不过,连续请求两次的情况倒是有,确实会造成资源的浪费,目前我也没有具体的解决方案:sweat:
    菜小东:以前用OkHttp我都是定义一个int值,然后每次请求就设置一个标志,到哪步出错就根据int值去判断!现在用RxJava把请求撸成一串了,这个中间请求失败的问题感觉不好处理!
  • markRao:作者,你好,麻烦问一个问题Retrofit和Rxjava包的版本依赖对应在哪里找?就是说假设有一个版本的包我怎么找它的依赖版本,谢谢
    markRao: @带心情去旅行 好的👌,谢谢啦
    带心情去旅行:这个我没仔细研究过,每次我都是选择最新的版本。版本是否有对应关系还有待确认,你可以试试,说不定没有。
    只要这三个的版本号一样就可以了。
    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    compile 'com.squareup.retrofit2:converter-gson:2.0.2'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
  • 1eba778a4d4d:学习。学习
  • markRao: compile 'io.reactivex:rxjava:x.y.z'
    这个包是什么包?
    带心情去旅行:@萬物并作吾以觀復 就是rxjava的包,之前的一个版本
  • f6254973de38:楼主你好,按照楼主的做法,首先,我在编译时会报出com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK META-INF/rxjava.properties这问题。在网上的解决办法是添加: packagingOptions {
    exclude 'META-INF/rxjava.xml'
    exclude 'META-INF/rxjava.properties'
    }

    问题解决之后,在转换为Observable时会报出:java.lang.IllegalArgumentException: Unable to create call adapter for io.reactivex.Observable<com.price.take_new.Example> for method NewsService.getUser 错误,依赖都加上了,不知楼主有遇过没
    f6254973de38:@带心情去旅行 加了,就是不能解析
    带心情去旅行:@辉丶 看看你new Retrofit的时候,有添加这句吗?.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
  • coexist:请问怎么样让cookie持久化
    带心情去旅行:@coexist 这个我也没过,你可以去查一下,我看到挺多的
  • c250f750f4d3:如果在gettoken的那一步出错了,错误信息也在同一个onerror里面看吗?
    带心情去旅行:@指尖上的蜗牛 是的,错误信息在同一个onError里面,不过要是第一个请求失败了,就不会调用flatmap中的第二个请求。
    那个getUser写得有误,应该是service.getUser(token),感谢指出~~
    c250f750f4d3:@指尖上的蜗牛 还有那个flatmap里面的getUser(token),是直接调用接口里面的方法吗?我怎么直接调用不了
  • 来世不做友人A:问个问题,比如网络请求失败,整个链式调用在哪执行??? 如果说在onerror的话那 flagmap能执行吗
    带心情去旅行:@来世不做友人A 如果login失败了,就不会调用flagmap和doOnNext,直接调用subscribe 中的onError方法。
  • 421d3c51d2b6:学习了!不错!解决了很多疑惑
    带心情去旅行:@浮兮流年 哈哈,希望能帮到你
  • fe1a354c7464:思路很清晰....受益良多..感谢......但是我怎么请求网络不成功,,,,
  • GEM的紫领巾:算是比较清晰的解析了,谢谢博主
    带心情去旅行:@GEM的紫领巾 希望能帮助到你
  • 7300981e9864:进阶中 如果登录失败,该怎么处理,判断登录失败返回 return Observable.just(null)
    带心情去旅行:@wenfeiyu @wenfeiyu 登录失败时,可以在onError判断Throwable是否为HttpException类型,HttpException包含了所有失败的原因。通过exception.response()可以得到Response,通过Response可以得到所有想要的数据。代码如下
    @Override
    public void onError(Throwable e) {
    if (e instanceof HttpException) {
    HttpException exception = (HttpException) e;
    if (exception == null) return;
    //从HttpException中获取Response,Response中包含了请求失败的信息
    Response response = exception.response();
    try {
    //获取服务器返回的字符串
    String errorBody = response.errorBody().string();
    Log.i(TAG, "login onError: " + errorBody == null ? errorBody : "");
    } catch (IOException e1) {
    e1.printStackTrace();
    }
    }
    }
  • 龙猫跑跑跑:如果网络请求失败了,在action1方法中只有一个call回调,这种情况怎么办?错误信息会在最后的error方法中出现吗?
    带心情去旅行:@Small_Cake 原来你说的是doOnNext中的Action1,我之前理解错了。
    请求失败的话,会调用最后那个Observer中的onError方法。文中的Action1只处理请求成功的情况。
    Small_Cake:不对啊,你说请求失败了,不会执行doOnNext方法啊,那么代替也就毫无意义了啊
    带心情去旅行:@BruceAndroid 不好意思,今天才看到评论。如果要处理请求失败的情况,可以使用 Observer代替Action1。
  • VZzzzzz:请问一下 加do的方法和不加do的方法区别在哪里呢,比如说OnNext和doOnNext方法有什么区别
    带心情去旅行:@俗套 OnNext和doOnNext都是请求成功后调用。当请求成功后会先调用doOnNext中保存用户信息的方法,然后才去执行OnNext中的方法。若请求失败,则不会调用doOnNext和OnNext中的方法。
  • 方志朋:楼主,好博文,可以给个源码不
    带心情去旅行:@行走的那些事 不好意思啊,以前做的例子,已经删掉了 :disappointed_relieved:
  • 44a4180ea99d: :flushed: 简单易懂,不过那个作者叫扔物线
    mocen_王琪:哈哈哈哈哈哈,笑死了
    CokeNello::joy: 我也交错他名字好多回。。。建议他改名?哈哈
  • RicardoMJiang:请求成功后切换到io线程执行保存用户信息的动作,最后再切换到主线程执行请求失败onError()、请求成功onNext()

    这一段,不应该成功后再保存用户信息吗?
    RicardoMJiang:原来是这样,解决了我一个困惑很久的问题
    带心情去旅行:@whuhan2013 我重新看了下,其实那样写是没错的。当请求成功后会先跳转到io线程,调用doOnNext中保存用户信息的方法,然后再跳转到主线程去执行OnNext中的方法。而请求失败是不执行doOnNext的,doOnNext只有在请求成功才调用,所以只要在saveUserInfo判断下是否保存就可以了。
    带心情去旅行:@whuhan2013 是的,业务上因该是请求成功后保存。这边提前保存是不大好
  • MarkBlue:楼主,请求的时候,公用的一些参数,如platform、version,key等 这些参数封装在哪里呢?
    带心情去旅行:@MarkBlue 给你推荐两个网址,应该能找到你要的答案
    http://www.voidcn.com/blog/ttdevs/article/p-6111103.html
    http://www.lmcw.cn/?id=47
    MarkBlue:@带心情去旅行 LoginApi.FamousService service = BaseApi.getRetrofit().create(LoginApi.FamousService.class);
    Map<String, String> options = new HashMap<String, String>();
    options.put("platform", "android");
    options.put("version", "1.0");
    options.put("key", "123456");
    options.put("Mobile", "15256298062");
    options.put("PassWord", "123456");
    Call<Result> call = service.getFamousList(options);像这样,只不过每次请求,都必要带着platform、version,key这些值,封装在哪里比较好啊,我对okhttp请求不是很清楚,请指点下!
    带心情去旅行:@青春在哪里 是这样子的吗?
    static final String version = "version=1&key=xxxxx";
    @get("user/login?" + version)
    Call<UserInfo> login(
    @Query("username") String username,
    @Query("password") String password
    );
  • Jafir:最后那个例子,抛物线的例子可以没有写doOnNext方法,而且里面还传的是userInfo
    带心情去旅行:@Jafir 用了他的例子,根据自己的代码做了点改动
  • 一杯茶一本书:不错,最近这个挺火
  • ibrucekong:学习了,mark
  • HCam:希望作者能坚持这样的分享
  • Tang1024:思路很清晰,秒懂 :+1:
  • yangjianan:学习了,希望继续更新博文:grin:
  • Android首席学徒:加油,谢谢

本文标题:【Android】RxJava + Retrofit完成网络请求

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