美文网首页Android 入门进阶android 开发android
Android RxJava2+Retrofit2搭建网络请求框

Android RxJava2+Retrofit2搭建网络请求框

作者: Jaycee88 | 来源:发表于2017-03-08 22:28 被阅读22003次

    前言

    之前做网络请求,用的是android-async-http,基于HttpClient 的,虽然早已淘汰,但一直懒得换,前一段时间看了stormzhang的《2016 Android Top 10 Library》文章,提到RxJava+Retrofit 是完美搭配,所以下定决定重构一下现在的项目

    网上查了一些资料,遇到了一些小坎坷,终于搞定了,因为网上查到的一些文章大多都是半年以前的,而我使用的都是最新的库,遇到了一些新的问题,所以感觉有必要写篇文章帮助后人少走一些弯路

    本文默认读者对RxJava和Retrofit 已经有了一定的了解,若对RxJava和Retrofit 还不了解,请先查阅相关资料

    使用

    1、添加依赖库

    compile "io.reactivex.rxjava2:rxjava:2.1.1"
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    compile 'com.squareup.retrofit2:retrofit:2.3.0'
    compile 'com.squareup.retrofit2:converter-gson:2.3.0'
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
    

    converter-gson是Retrofit到Gson进行转换的库,adapter-rxjava2是Retrofit到RxJava进行转换的库

    这里我是采用Google Gson进行数据解析的,如果你使用的是Jackson,替换为如下依赖即可

    compile 'com.squareup.retrofit2:converter-jackson:2.3.0'
    

    如果需要添加HttpLoggingInterceptor进行调试,添加如下依赖

    compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'
    

    2、写一个Service

    public interface RetrofitService {
        @FormUrlEncoded
        @POST("account/login")
        Observable<BaseEntity<UserInfo>> login(
                @Field("userId") String userId,
                @Field("password") String password
        );
    
        @GET("video/getUrl")
        Observable<BaseEntity<VideoUrl>> getVideoUrl(
                @Query("id") long id
        );
    
        @FormUrlEncoded
        @POST("user/addVideo")
        Observable<BaseEntity<Boolean>> addVideo(
                @FieldMap Map<String, Object> map
        );
    }
    

    相对于单独使用Retrofit,该处返回的是Observable对象

    3、通常服务器端会返回统一的数据格式,这里我们写一个BaseEntity

    public class BaseEntity<E> {
    
        @SerializedName("code")
        private int code;
        @SerializedName("msg")
        private String msg;
        @SerializedName("data")
        private E data;
    
        public boolean isSuccess() {
            return code == 0;
        }
    
        public int getCode() {
            return code;
        }
    
        public void setCode(int code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public E getData() {
            return data;
        }
    
        public void setData(E data) {
            this.data = data;
        }
    }
    

    4、然后我们可以封装一个RetrofitFactory

    public class RetrofitFactory {
    
        private static final String BASE_URL = "http://api.baidu.com/";
    
        private static final long TIMEOUT = 30;
    
        // Retrofit是基于OkHttpClient的,可以创建一个OkHttpClient进行一些配置
        private static OkHttpClient httpClient = new OkHttpClient.Builder()
                // 添加通用的Header
                .addInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Request.Builder builder = chain.request().newBuilder();
                        builder.addHeader("token", "123");
                        return chain.proceed(builder.build());
                    }
                })
                /*
                这里可以添加一个HttpLoggingInterceptor,因为Retrofit封装好了从Http请求到解析,
                出了bug很难找出来问题,添加HttpLoggingInterceptor拦截器方便调试接口
                 */
                .addInterceptor(new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                    @Override
                    public void log(String message) {
    
                    }
                }).setLevel(HttpLoggingInterceptor.Level.BASIC))
                .connectTimeout(TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(TIMEOUT, TimeUnit.SECONDS)
                .build();
    
        private static RetrofitService retrofitService = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                // 添加Gson转换器
                .addConverterFactory(GsonConverterFactory.create(buildGson()))
                // 添加Retrofit到RxJava的转换器
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(httpClient)
                .build()
                .create(RetrofitService.class);
    
        public static RetrofitService getInstance() {
            return retrofitService;
        }
    
        private static Gson buildGson() {
            return new GsonBuilder()
                    .serializeNulls()
                    .setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
                    // 此处可以添加Gson 自定义TypeAdapter
                    .registerTypeAdapter(UserInfo.class, new UserInfoTypeAdapter())
                    .create();
        }
    }
    

    5、通常我们会在IO线程进行请求,在主线程进行回调

    public class RxSchedulers {
    
        public static <T> ObservableTransformer<T, T> compose() {
            return new ObservableTransformer<T, T>() {
                @Override
                public ObservableSource<T> apply(Observable<T> observable) {
                    return observable
                            .subscribeOn(Schedulers.io())
                            .doOnSubscribe(new Consumer<Disposable>() {
                                @Override
                                public void accept(Disposable disposable) throws Exception {
                                    if (!Utils.isNetworkConnected()) {
                                        Toast.makeText(context, R.string.toast_network_error, Toast.LENGTH_SHORT).show();
                                    }
                                }
                            })
                            .observeOn(AndroidSchedulers.mainThread());
                }
            };
        }
    }
    

    这里我们可以添加一个通用的网络连接判断

    6、RxJava Observable 订阅需要传入一个Observer对象,此处封装一个BaseObserver

    public abstract class BaseObserver<T> implements Observer<BaseEntity<T>> {
    
        private static final String TAG = "BaseObserver";
        private Context mContext;
    
        protected BaseObserver(Context context) {
            this.mContext = context.getApplicationContext();
        }
    
        @Override
        public void onSubscribe(Disposable d) {
    
        }
    
        @Override
        public void onNext(BaseEntity<T> value) {
            if (value.isSuccess()) {
                T t = value.getData();
                onHandleSuccess(t);
            } else {
                onHandleError(value.getMsg());
            }
        }
    
        @Override
        public void onError(Throwable e) {
            Log.e(TAG, "error:" + e.toString());
        }
    
        @Override
        public void onComplete() {
            Log.d(TAG, "onComplete");
        }
    
    
        protected abstract void onHandleSuccess(T t);
    
        protected void onHandleError(String msg) {
            Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
        }
    }
    

    7、调用

    private void login(String userId, String password) {
        Observable<BaseEntity<UserInfo>> observable = RetrofitFactory.getInstance().login(userId, password);
        observable.compose(RxSchedulers.compose()).subscribe(new BaseObserver<UserInfo>(context) {
             @Override
              protected void onHandleSuccess(UserInfo userInfo) {
                   // 保存用户信息等操作
              }
        });
    }
    

    RxJava生命周期管理

    可以用RxLifecycle来管理RxJava的生命周期
    RxLifecycle:https://github.com/trello/RxLifecycle/tree/2.x

    小结

    重构之后发现Retrofit搭配RxJava之后,绝对是最好用的网络请求库,没有之一
    如有问题,欢迎留言指正

    Demo地址:https://github.com/jaycee88/RxJavaRetrofitDemo

    参考文章:http://www.jianshu.com/p/1fb294ec7e3b
    http://blog.csdn.net/gesanri/article/details/52701651

    最后

    向大家推荐《进阶全栈工程师之路》
    https://xiaozhuanlan.com/fullstack?rel=4605162920

    相关文章

      网友评论

      • 南柯一梦丶_6b23:你好,我想问下我同一个页面 复用的同一个Observer<T>,假如一个页面上同时发送3个网络请求,在onNext里面 我无法区分三个网络请求无序返回的bean的赋值逻辑(不想使用baseBean instanceof xxxBean来判断) 我的想法是将每个网络请求发送时设置一个tag 网络请求返回后 拿到这个tag,用tag来判断当前返回的bean属于哪个网络请求,但是没有再retrofit和rx中找到对应的方法,请问我该怎么处理,这问题卡了好久了 在线急等。
      • 90df3cf952b6:大神你好,我最近有个需求,就是所有的接口当BaseEntity返回的code是0的话就是正常成功,但如果code是100的话,就是需要强制更新,然后里面的data返回的数据结构就跟我原来接口的数据结构不一样,就是原本可能是返回一个user对象或者其他对象的,是所有的接口都有可能会返回其他的字段,那我有什么方法可以获取到data里面的json?或者替换成另外一个对象?
      • 小新哥的大梦想:楼主可以发一个Demo吗?谢谢
        小新哥的大梦想:@Jaycee88 :disappointed_relieved: 好的,才看到谢谢。讲的不错,赞一个。。。。
        Jaycee88:文章结尾有啊!
      • da2125534228:你好,请问我照你的写完以后,调用时候subscribe(new BaseObserver..)这里会报错,cannot resolve method'subscribe',是什么原因你知道吗?昨天研究到十点也没发现这个是错在哪了
        Jaycee88:@Ajax_ed34 细节问题,RxJava版本或IDE版本问题
        da2125534228:是的,我用的是rxjava2.1.7,可是我换成2.1.1还是不行
        Jaycee88:你看看是不是RxJava的版本不太一样
      • cbe5621fb4dd:楼主你好 我就是看了你的这篇文章才开始使用rxjava+retrofit进行网络请求的 我现在有一个需求 一开始延迟一段时间进行网络请求 如果返回结果不成功那么再延迟几秒继续调用这个请求 知道返回成功 或者用户关闭页面 请问改怎么做 如看到请回答详细一些 最好有示例代码 感激不尽
        Jaycee88:那你就在Activity中写一个定时任务呗
      • 我吃大西瓜QAQ:楼主你好,使用BaseObserver之后,返回的数据是空的,不使用BaseObserver就可以正常返回数据,请问这是怎么回事?
        java.lang.IllegalStateException: Fatal Exception thrown on Scheduler.
        at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:111)
        Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.example.mypc.rxjavaretrofitmvp.model.bean.Weather$DataBean.getGanmao()' on a null object reference
        我吃大西瓜QAQ:@Jaycee88 谢谢,我好像找到原因了,我用的这个API返回的结果是个Object ,所以Json解析出错导致的.:joy:
        Jaycee88:空指针了不是
      • 90df3cf952b6:大神,我也是来请教一下取消网络请求的方法的.由于刚接触RxJava和Retrofit,不是很懂,看了你的demo,是不是在.doOnSubscribe(new Consumer<Disposable>() {xxxxxx }) 里面写取消请求的处理? 我的想法是在关闭progressDialog的时候取消请求,假如是按照你这样封装,我可以在什么地方调用对应的Disposable.dispose()方法呢?
        90df3cf952b6:@Jaycee88 十分感谢!
        Jaycee88:ProgressDialog通过构造方法传到BaseObserver中,在BaseObserver中设置ProgressDialog的OnCancelListener,在OnCancelListener中取消请求(调用Disposable的dispose方法)
      • Nimodou:你好在吗, 我按照你的思路写了个例子,我的BaseEntity的T 是直接一个集合,我写成 BaseObserver<List<Subject>> subscriber,每一次 返回的数据都是空,是怎么回事,
        Jaycee88:@Nimodou 好的
        Nimodou:@Jaycee88 我自己的锅,我写错了字段名。。。,我现在重新封装下
        Jaycee88:报什么错误?
      • a05e5cb6c47f:大佬,咱这个封装的这个data如果是空的怎么办,error:com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 70 path $.result。这个直接就被拦截器给拦截了。
        Jaycee88:@咸鱼在北冥 :joy:
        a05e5cb6c47f:@Jaycee88 大拿,360度服你
        Jaycee88:目前我是注册TypeAdapter,暂时没找到更好的解决办法
      • 烧烤摊前卖烧烤:您好,问一下混淆怎么配置?
        Jaycee88:不需要配置混淆
      • a05e5cb6c47f:大佬,我json中的result是字符串,那我BaseEntity里该怎么填啊,泛型老报错java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 70 path $.result。怎么解决啊
      • a05e5cb6c47f:大神,你这Typeadapter是不是每解析个json字符串都要写一个是吧
        Jaycee88:@咸鱼在北冥 是的是的,我的也是报这个错误:Expected BEGIN_OBJECT but was STRING
        a05e5cb6c47f:@Jaycee88 我现在遇到的问题应该跟你说的很像。是不是也要建一个Typeadaoter,IllegalStateException: Expected BEGIN_OBJECT but was STRING 。接口中的result按说应该是实体类,但是返回的是空,然后就是这报了这个
        Jaycee88:并不需要每一个接口返回的对象都注册一个TypeAdapter,我当时注册UserTypeAdapter只是为了解决我们后台接口返回数据不规范的问题,例如:User,正常返回的时候数据是{id: 123, nickname: abc, age: 25},异常返回的时候数据就是“”空字符串,空字符串Gson解析为User对象就会抛出异常,这是不规范的,但是没办法,历史遗留问题
      • 丶隔壁大花:大神,请问在哪里可以打印最后拼接好的链接,和服务器返回的JSON啊?求告知,感激不尽。
        丶隔壁大花:@Jaycee88 能打印最后拼接好的链接了,但是还是没有找到返回的JSON在哪里打印
        丶隔壁大花:@Jaycee88 大佬,我还是没成功欸,能不能加我QQ指导一下,感激不尽。QQ2920501612
        Jaycee88:在OkHttpClient添加的HttpLoggingInterceptor中打印URL和服务器返回的数据,Level设置成:HttpLoggingInterceptor.Level.BODY
      • 2c4117a9984b:这里调用的时候使用内部类 BaseObserver,有没有考虑内存泄漏的问题?
        丶隔壁大花:大神,请问在哪里可以打印最后拼接好的链接,和服务器返回的JSON啊?求告知,感激不尽。
        Jaycee88:有道理,我考虑下
      • 千雨茶:你好,这样定义BaseEntity,是按照服务器返回的固定结构定义的,这个固定结构是怎样的呀?是分别是state、message、data这三个为键的键值对吗?
        Jaycee88:看你们后台接口的设计啊
      • 繁复至极返璞归简:楼主,所有的接口请求都要在RetrofitService中定义一个方法吗?
        Jaycee88:@繁复至极返璞归简 你可以按不同的功能模块,分成几个RetrofitService,类似string和style文件,多的话也是可以分开的
        繁复至极返璞归简:@Jaycee88 我们的app有300多个接口,那岂不是都要通过RetrofitService.xxxXX方法调用?那这个类300多个方法也忒臃肿了……
        Jaycee88:是的
      • _孑孓_:查了不少文章,还是版本冲突,java.lang.NoSuchMethodError: No virtual method isSuccessful()Z in class Lretrofit2/Response; or its super classes (declaration of 'retrofit2.Response' appears in /data/app/com.moon.samples-1/base.apk) 求解怎么处理这个问题
        Jaycee88:仔细分析下,看下评论,之前有人说过这个问题
      • doc__wei:直接用连最基本的请求都请求不了
        Jaycee88:不能够啊,很多人都用过的!
      • reader_b8f9:卤煮,onstart方法在哪里,我要添加进度条往哪加
        Jaycee88:@reader_b8f9 Observer中onSubscribe方法会传进来一个Disposable对象,可以调用Disposable.dispose()方法取消请求,也可以用RxLifecycle来管理生命周期:https://github.com/trello/RxLifecycle/tree/2.x
        reader_b8f9:@Jaycee88 感谢,还有个问题就是用户点击按钮请求但是瞬间退出了当前界面怎么在ondestroy方法里进行取消请求
        Jaycee88:可参考这篇文章:http://blog.csdn.net/gesanri/article/details/52701651
      • doc__wei:楼主,写错了啊,我被坑了一天,添加通用头
        // 添加通用的Header .addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request.Builder builder = chain.request().newBuilder(); builder.addHeader("token", "123"); return chain.proceed(builder.build()); } }
        加上这个:::::
        Request.Builder requestBuilder =builder.method(originalRequest.method(), originalRequest.body());
        Request request = requestBuilder.build();
        Jaycee88:不太明白你要表达什么?为什么要加后边3行呢?
      • reader_b8f9:请问支持下载文件和上传吗
        Jaycee88:@reader_b8f9 是的,上传你可以用MultipartBody.Part,下载返回类型设成ResponseBody
        reader_b8f9:@Jaycee88 您这demo里好像没有上传和下载啊
        Jaycee88:支持的
      • 178b3a6fef2c:楼主,我按照你github上的demo进行封装后,发现请求成功了,服务器也正确的给我返回数据了,但是BaseEntity里面并没有接收到数据这是什么原因呢
        reader_b8f9:兄弟,我也遇到这个问题,你是怎么解决的?
        Jaycee88:@嗨阿诺德 哈哈
        178b3a6fef2c:我自己原因。。楼主请忽略。。
      • 天心映月:onNex执行后,onComplete为什么最终没有执行
        Jaycee88:@天心映月 我刚才试了,有执行的
        天心映月: @Jaycee88 我在baseoberver中打印,没有打印信息
        Jaycee88:有执行的呀
      • fe58ab2e6d80:大神,想你请教一下取消网络请求的方法
        Jaycee88:@青春不迷茫_Lee adapter-rxjava2中,Disposable.dispose()会同时取消Retrofit的网络请求的,你可以看下源码
        谁的春春不迷茫:@Jaycee88 楼主你好,Disposable.dispose()只是用于中断两者的连接,导致接受者就收不到事件,但是发送者会继续发送事件,所以应该同时断开Retrofit的网络请求,你认为呢
        Jaycee88:调用Disposable.dispose()方法
      • 1fbcb666342c:Hello!
        observable.compose(RxSchedulers.compose())
        这一步会提示无法转化Object到指定的泛型
        改成
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        就正常了,相当于不封装,这样实际项目会重复代码。

        .subscribe(new BaseObserver<UserInfo>(context)
        这部分又会提醒接口方法部分未实现

        求解答
        :joy:
        1fbcb666342c:@Jaycee88 我也觉得,然后我把databinding包删了……,切换还要转化才可以,今天在重新弄个试试:pensive:
        Jaycee88:你是不是哪里导包导错了?我这样写没有问题呀
        1fbcb666342c:Error:(46, 64) 错误: <匿名com.example.cheng.myaccountbook.activity.MainActivity$1>不是抽象的, 并且未覆盖Observer中的抽象方法onSubscribe(Disposable)
        Error:(43, 17) 错误: 无法将类 Observable<T>中的方法 compose应用到给定类型;
        需要: ObservableTransformer<BaseResponse<UserInfo>,R>
        找到: ObservableTransformer<Object,Object>
        原因: 无法推断类型变量 R
        (参数不匹配; ObservableTransformer<Object,Object>无法转换为ObservableTransformer<BaseResponse<UserInfo>,R>)
        其中, R,T是类型变量:
        R扩展已在方法 <R>compose(ObservableTransformer<T,R>)中声明的Object
        T扩展已在类 Observable中声明的Object
      • 痞子1号:你好 按照你的来做,添加订阅的时报 无法解析该发法!请问可以解答吗?或者给一份源码,我看一下!谢谢!
        Jaycee88:日志贴出来我看下
      • 小谷xg:貌似没有取消网络请求
        小谷xg:@Jaycee88 :+1:
        Jaycee88:是的,谢谢提醒,只用了RxLifecycle管理了RxJava的生命周期,adapter-rxjava把Call对象转换成了Observable对象,我得看下源码,看下怎么处理这个问题
      • GaoYuan117:大神,使用retrofit的时候每个url都要创建一个接口吗
        GaoYuan117:@Jaycee88 最近在看rxjava2.0和retofit的封装 ,好纠结
        Jaycee88:我更新了文章,你再看下RetrofitService类
        Jaycee88: 不是的,每一个url,你在RetrofitService类中添加一个方法就行了,大神不敢当!

      本文标题:Android RxJava2+Retrofit2搭建网络请求框

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