美文网首页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