RxJava2 实战知识梳理(4) - 结合 Retrofit 请求新闻资讯

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 知识梳理系列:

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

推荐阅读更多精彩内容