美文网首页Android开发RxJavaAndroid Rxjava
RxJava2 实战知识梳理(4) - 结合 Retrofit

RxJava2 实战知识梳理(4) - 结合 Retrofit

作者: 泽毛 | 来源:发表于2017-08-28 21:42 被阅读2065次

    RxJava2 实战系列文章

    RxJava2 实战知识梳理(1) - 后台执行耗时操作,实时通知 UI 更新
    RxJava2 实战知识梳理(2) - 计算一段时间内数据的平均值
    RxJava2 实战知识梳理(3) - 优化搜索联想功能
    RxJava2 实战知识梳理(4) - 结合 Retrofit 请求新闻资讯
    RxJava2 实战知识梳理(5) - 简单及进阶的轮询操作
    RxJava2 实战知识梳理(6) - 基于错误类型的重试请求
    RxJava2 实战知识梳理(7) - 基于 combineLatest 实现的输入表单验证
    RxJava2 实战知识梳理(8) - 使用 publish + merge 优化先加载缓存,再读取网络数据的请求过程
    RxJava2 实战知识梳理(9) - 使用 timer/interval/delay 实现任务调度
    RxJava2 实战知识梳理(10) - 屏幕旋转导致 Activity 重建时恢复任务
    RxJava2 实战知识梳理(11) - 检测网络状态并自动重试请求
    RxJava2 实战知识梳理(12) - 实战讲解 publish & replay & share & refCount & autoConnect
    RxJava2 实战知识梳理(13) - 如何使得错误发生时不自动停止订阅关系
    RxJava2 实战知识梳理(14) - 在 token 过期时,刷新过期 token 并重新发起请求
    RxJava2 实战知识梳理(15) - 实现一个简单的 MVP + RxJava + Retrofit 应用


    一、前言

    如何通过结合Retrofit框架来进行网络请求,也是RxJava的学习过程中必须要掌握的一环。网上已经有很多开源项目和文章介绍了,今天这篇文章,我们就通过一个简单的例子,通过RxJava + Retrofit的方式实现网络请求。

    这个例子很简单,我们通过 干货集中营 提供的接口,分别请求Android类和iOS类的资讯,并将这两个接口所返回的数据在界面上进行展示。

    通过该例子,可以学习如何将RetrofitRxJava结合,并通过zip操作符实现等待多个网络请求完成。

    二、示例

    2.1 接口介绍

    首先来熟悉一下所用到的测试接口,其数据来自于 干货集中营,这里选择AndroidiOS两类的资讯,通过接口的描述,可以知道发起请求时的变量包含三个:

    • 分类
    • 请求个数
    • 请求页数

    返回的数据格式如下:


    2.2 编写 Entity 类

    根据分析好的数据格式,我们编写对应的Entity类:

    • 单次返回结果的数据结构:
    public class NewsEntity {
    
        private boolean error;
        private List<NewsResultEntity> results = new ArrayList<>();
    
        public boolean isError() {
            return error;
        }
    
        public void setError(boolean error) {
            this.error = error;
        }
    
        public List<NewsResultEntity> getResults() {
            return results;
        }
    
        public void setResults(List<NewsResultEntity> results) {
            this.results = results;
        }
    }
    
    • 单条资讯的数据结构:
    public class NewsResultEntity {
    
        private String type;
        private String publishedAt;
        private String desc;
        private String who;
    
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    
        public String getPublishedAt() {
            return publishedAt;
        }
    
        public void setPublishedAt(String publishedAt) {
            this.publishedAt = publishedAt;
        }
    
        public String getDesc() {
            return desc;
        }
    
        public void setDesc(String desc) {
            this.desc = desc;
        }
    
        public String getWho() {
            return who;
        }
    
        public void setWho(String who) {
            this.who = who;
        }
    }
    

    2.3 引入 Retrofit 依赖

    接下来,在build.gradle文件中,引入必要的依赖,以下三个依赖包的作用分别为:

    • Retrofit的核心库
    • 将返回的Call<Response>转换成Call<NewsEntity>
    • Call<NewsEntity>转换成Observable<NewsEntity>
    dependencies {
         //省略....
        compile 'com.squareup.retrofit2:retrofit:2.1.0'
        compile 'com.squareup.retrofit2:converter-gson:2.0.0'
        compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
    
    }
    

    最后别忘了,在AndroidManifest.xml中声明必要的网络权限:

    <uses-permission android:name="android.permission.INTERNET"/>
    

    2.4 定义 Retrofit 需要的请求接口

    按照Retrofit的使用介绍,我们需要定义一个接口类,这个接口类的返回值为Observable<NewsEntity>,也就是我们之前定义好的数据结构。而这个接口接收三个参数:请求类型、请求个数、请求所在页数。

    public interface NewsApi {
    
        @GET("api/data/{category}/{count}/{page}")
        Observable<NewsEntity> getNews(@Path("category") String category, @Path("count") int count, @Path("page") int page);
    }
    

    当我们需要请求数据时,就应当像下面这样构造一个Observable<NewsEntity>

    • baseUrl:定义请求链接的前缀
    • addConverterFactory:将OKHttp返回的标准Response解析成我们所需要的数据类型NewsEntity
    • addCallAdapterFactory:将Call<NewsEntity>转换成Observable<NewsEntity>,这样才能真正将RetrofitRxJava结合起来。
        private Observable<NewsEntity> getObservable(String category, int page) {
            NewsApi api = new Retrofit.Builder()
                    .baseUrl("http://gank.io")
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build().create(NewsApi.class);
            return api.getNews(category, 10, page);
        }
    

    2.5 发起请求

    以上就是所有的准备工作,回顾一下我们主要做了以下四步,这也是今后我们使用其它任意接口时的标准流程:

    • 熟悉接口
    • 根据接口返回的数据,定义Entity
    • 根据接口的url组成方式定义Retrofit所需要的接口声明,接口函数的返回类型为Observable<Entity>,其中Entity就是第二步中定义好的返回数据类型。
    • 通过Retrofit,根据第三步的接口定义,返回真正的Observable

    其实经过以上的四步,我们的工作就基本上完成了,只需要把上面第四步中返回的Observable<XXXEntity>当做一个发送数据的普通数据源就可以了。

    示例代码如下,我们请求了AndroidiOS两个接口,并且使用zip操作符让两个接口都返回之后,才将数据呈现给用户,同时每次点击刷新资讯之后,我们将页数增加一以请求新的资讯。

    public class NewsActivity extends AppCompatActivity {
    
        private int mCurrentPage = 1;
        private NewsAdapter mNewsAdapter;
        private List<NewsResultEntity> mNewsResultEntities = new ArrayList<>();
        private CompositeDisposable mCompositeDisposable = new CompositeDisposable();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_news);
            initView();
        }
    
        private void initView() {
            Button btRefresh = (Button) findViewById(R.id.bt_refresh);
            btRefresh.setOnClickListener(new View.OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    refreshArticle(++mCurrentPage);
                }
            });
            RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_news);
            LinearLayoutManager layoutManager = new LinearLayoutManager(this);
            recyclerView.setLayoutManager(layoutManager);
            mNewsAdapter = new NewsAdapter(mNewsResultEntities);
            recyclerView.setAdapter(mNewsAdapter);
            refreshArticle(++mCurrentPage);
        }
    
        private void refreshArticle(int page) {
           Observable<List<NewsResultEntity>> observable = Observable.just(page).subscribeOn(Schedulers.io()).flatMap(new Function<Integer, ObservableSource<List<NewsResultEntity>>>() {
    
                @Override
                public ObservableSource<List<NewsResultEntity>> apply(Integer page) throws Exception {
                    Observable<NewsEntity> androidNews = getObservable("Android", page);
                    Observable<NewsEntity> iosNews = getObservable("iOS", page);
                    return Observable.zip(androidNews, iosNews, new BiFunction<NewsEntity, NewsEntity, List<NewsResultEntity>>() {
    
                        @Override
                        public List<NewsResultEntity> apply(NewsEntity androidEntity, NewsEntity iosEntity) throws Exception {
                            List<NewsResultEntity> result = new ArrayList<>();
                            result.addAll(androidEntity.getResults());
                            result.addAll(iosEntity.getResults());
                            return result;
                        }
                    });
                }
            });
            DisposableObserver<List<NewsResultEntity>> disposable = new DisposableObserver<List<NewsResultEntity>>() {
    
                @Override
                public void onNext(List<NewsResultEntity> value) {
                    mNewsResultEntities.clear();
                    mNewsResultEntities.addAll(value);
                    mNewsAdapter.notifyDataSetChanged();
                }
    
                @Override
                public void onError(Throwable e) {
    
                }
    
                @Override
                public void onComplete() {
    
                }
            };
            observable.observeOn(AndroidSchedulers.mainThread()).subscribe(disposable);
            mCompositeDisposable.add(disposable);
        }
    
        private Observable<NewsEntity> getObservable(String category, int page) {
            NewsApi api = new Retrofit.Builder()
                    .baseUrl("http://gank.io")
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build().create(NewsApi.class);
            return api.getNews(category, 10, page);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mCompositeDisposable.clear();
        }
    }
    

    运行结果为:


    三、示例解析

    关于如何使用Retrofit + RxJava前面已经说得比较清楚了,下面我们重点介绍一下新接触的两个操作符,flatMapzip

    3.1 flatMap

    flatMap的原理图如下所示:


    它接收一个Function函数,对于上游发送的每个事件它都会应用该函数,这个函数返回一个新的Observable,如果有多个Observable,那么他会发送合并后的结果。

    在上面的例子中,上游的just发送一个请求的所在页数,我们根据这个页数再去创建一个新的Observable来发送数据。

    3.2 zip

    zip操作符的原理图如下所示:


    它接收多个Observable,以及一个函数,该函数的形参为这些Observable发送的数据,并且要等所有的Observable都发射完会后才会回调该函数。

    通过zip操作符,我们就可以实现等待多个网络请求完成再返回的需求,例如在上面的例子中,我们会等待AndroidiOS类的资讯请求都返回之后,再合并它们的结果发送给下游,在界面上展示。


    更多文章,欢迎访问我的 Android 知识梳理系列:

    相关文章

      网友评论

      • Mike张小多:对于3.2节的zip的解释我有一个异议:文中说“它接收多个Observable,以及一个函数,该函数的形参为这些Observable发送的数据,并且要等所有的Observable都发射完会后才会回调该函数。”这种情况应该是同步情况下才会如此吧?异步的时候应该是多个Observable各自发送第一次后先组合起来发送给下游处理一次,然后再发第二次、第三次...一直到发送量最小的那个Observable发送完它的最后一个事件,下游便不再接受数据。
        泽毛:@Mike张小多 嗯嗯,我也是这么理解的,你表述的比较清楚,我修改一下。
      • 明朗__:如果每个接口请求下来的数据实体是不一样情况 怎么去做呢
        泽毛:zip 不要求两个 Observable<T> 中 T 的类型是相同的,可以写两个不同类型的 Observable,然后在 apply 方法里处理就好了。

      本文标题:RxJava2 实战知识梳理(4) - 结合 Retrofit

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